Apache VirtualHosts allow you to host multiple websites on a single server, each with its own domain name, document root, and configuration. On Ubuntu and Debian-based systems, Apache uses a specific directory structure for managing VirtualHosts that differs from CentOS/RHEL. This guide covers the full setup process, including SSL configuration and common troubleshooting.
Understanding sites-available vs sites-enabled
Ubuntu's Apache uses a two-directory system:
/etc/apache2/sites-available/— Contains all VirtualHost configuration files, whether active or not. This is where you create and edit your configs./etc/apache2/sites-enabled/— Contains symbolic links to the configs insites-availablethat are currently active. Apache only loads configs from this directory.
You never edit files in sites-enabled directly. Instead, you create configs in sites-available and use a2ensite/a2dissite to enable/disable them.
Creating a VirtualHost Configuration
Create a new configuration file in sites-available:
sudo nano /etc/apache2/sites-available/example.com.conf
Add the following basic VirtualHost configuration:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
ServerAdmin webmaster@example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
Options -Indexes +FollowSymLinks -MultiViews
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
Explanation of Each Directive
<VirtualHost *:80>— Listen on all IP addresses on port 80 (HTTP).ServerName— The primary domain name for this site. Apache uses this to match incoming requests to the correct VirtualHost.ServerAlias— Additional domain names that should also match this VirtualHost (e.g., the www subdomain).DocumentRoot— The filesystem path where the website files are located.Options -Indexes— Prevents directory listing when no index file exists.Options +FollowSymLinks— Allows Apache to follow symbolic links (required for many .htaccess rules).AllowOverride All— Allows.htaccessfiles to override server configuration (needed for WordPress, Laravel, etc.).Require all granted— Allows access to this directory from all clients.ErrorLog/CustomLog— Per-site log files for easier debugging.
Creating the Document Root
# Create the directory structure:
sudo mkdir -p /var/www/example.com/public_html
# Set ownership to your user (for uploading files):
sudo chown -R $USER:$USER /var/www/example.com/public_html
# Set correct permissions:
sudo chmod -R 755 /var/www/example.com
# Create a test index file:
echo "<h1>example.com is working</h1>" > /var/www/example.com/public_html/index.html
Enabling the Site
# Enable the new site:
sudo a2ensite example.com.conf
# Test the configuration for syntax errors:
sudo apache2ctl configtest
# Output: Syntax OK
# Reload Apache to apply changes:
sudo systemctl reload apache2
To disable a site later:
sudo a2dissite example.com.conf
sudo systemctl reload apache2
Disabling the Default Site
Ubuntu ships with a default VirtualHost (000-default.conf) that serves the Apache default page. Once you have your own VirtualHosts configured, you should disable it:
sudo a2dissite 000-default.conf
sudo systemctl reload apache2
If no VirtualHost matches an incoming request's Host header, Apache uses the first VirtualHost defined (alphabetically by filename). Disabling the default prevents it from being the catch-all.
Adding SSL (HTTPS) with Let's Encrypt
For production sites, you need an SSL VirtualHost on port 443. The easiest way is to use Certbot with Let's Encrypt:
# Install Certbot:
sudo apt install certbot python3-certbot-apache
# Obtain and install the certificate:
sudo certbot --apache -d example.com -d www.example.com
Certbot will automatically create an SSL VirtualHost configuration. If you prefer to configure SSL manually:
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
Options -Indexes +FollowSymLinks -MultiViews
AllowOverride All
Require all granted
</Directory>
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Modern SSL configuration:
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
# Security headers:
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
ErrorLog ${APACHE_LOG_DIR}/example.com-ssl-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-ssl-access.log combined
</VirtualHost>
Make sure the SSL module and headers module are enabled:
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod rewrite
sudo apache2ctl configtest
sudo systemctl reload apache2
HTTP to HTTPS Redirect
Update the port 80 VirtualHost to redirect all traffic to HTTPS:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>
Hosting Multiple Sites
To host multiple sites, create a separate configuration file for each:
# Create configs for each site:
/etc/apache2/sites-available/site1.com.conf
/etc/apache2/sites-available/site2.com.conf
/etc/apache2/sites-available/site3.com.conf
# Enable each:
sudo a2ensite site1.com.conf
sudo a2ensite site2.com.conf
sudo a2ensite site3.com.conf
sudo systemctl reload apache2
Apache routes requests based on the ServerName and ServerAlias directives. When a request comes in, Apache checks the Host header against all enabled VirtualHosts and uses the matching one.
Common Mistakes and Fixes
Missing ServerName
Without a ServerName, Apache cannot route requests to the correct VirtualHost. You may see a warning:
AH00558: apache2: Could not reliably determine the server's fully qualified domain name
Add ServerName to every VirtualHost and optionally set a global ServerName in /etc/apache2/apache2.conf.
Wrong DocumentRoot Permissions
If the DocumentRoot directory is not readable by the Apache user (www-data), you will get 403 Forbidden errors:
# Ensure Apache can read the directory:
sudo chmod 755 /var/www/example.com
sudo chmod 755 /var/www/example.com/public_html
AllowOverride None (Default)
If .htaccess rules are not working (WordPress permalinks broken, Laravel routes failing), check that AllowOverride is set to All, not None.
Forgetting to Enable mod_rewrite
sudo a2enmod rewrite
sudo systemctl restart apache2
Configuration Test Fails
Always test before reloading:
sudo apache2ctl configtest
If the test fails, the error message will point to the specific file and line number. Fix the issue before reloading. If you reload with a broken config, Apache may fail to restart entirely.
For Nginx-based setups as an alternative, see Nginx, PHP, and SSL on Ubuntu. For securing your VirtualHosts with security headers, see configuring security headers.