Back to Troubleshooting

Apache VirtualHosts on Ubuntu | NOC.org Support

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 in sites-available that 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 .htaccess files 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.

Improve Your Websites Speed and Security

14 days free trial. No credit card required.