Hosting more than one site on a single server becomes predictable when each site gets its own Apache virtual host, its own DocumentRoot, and its own logs. A clean virtual host setup makes staging vs production separation easy and keeps the “why is site A showing site B’s homepage?” mystery firmly in the genre of historical fiction.

Name-based virtual hosting works by matching the incoming request’s Host header against the ServerName and ServerAlias directives inside each `<VirtualHost>` block. Once a match is found, Apache applies the virtual host’s rules (directory permissions, directory options, logging) and serves content from the configured DocumentRoot.

The steps below use the Ubuntu and Debian layout where site files live in /etc/apache2/sites-available and are enabled via symlinks in /etc/apache2/sites-enabled using a2ensite. On RHEL-style systems, site snippets are typically placed in /etc/httpd/conf.d and the service name is httpd, so paths and service commands differ. A syntax check before reloading prevents a broken config from blocking the reload and leaving changes unapplied.

Steps to create a virtual host in Apache:

  1. Select the hostname for the new site.

    Example hostname: example.test
    Example document root: /var/www/example.test/public

  2. Create the document root directory.
    $ sudo mkdir --parents /var/www/example.test/public
  3. Create a simple test page in the document root.
    $ sudo tee /var/www/example.test/public/index.html >/dev/null <<'EOF'
    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>example.test</title>
      </head>
      <body>
        <h1>example.test virtual host works</h1>
      </body>
    </html>
    EOF
  4. Create the virtual host configuration in /etc/apache2/sites-available.
    $ sudo tee /etc/apache2/sites-available/example.test.conf >/dev/null <<'EOF'
    <VirtualHost *:80>
      ServerName example.test
      ServerAlias www.example.test
    
      DocumentRoot /var/www/example.test/public
    
      <Directory /var/www/example.test/public>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
      </Directory>
    
      ErrorLog ${APACHE_LOG_DIR}/example.test-error.log
      CustomLog ${APACHE_LOG_DIR}/example.test-access.log combined
    </VirtualHost>
    EOF

    AllowOverride None disables .htaccess processing for performance and clarity; switch to AllowOverride All only when an application requires it.

  5. Enable the new site configuration.
    $ sudo a2ensite example.test.conf
    Enabling site example.test.
    To activate the new configuration, you need to run:
      systemctl reload apache2

    /etc/apache2/sites-enabled/000-default.conf remains the fallback vhost for unknown hostnames; disable it with sudo a2dissite 000-default.conf when a catch-all default site is not desired.

  6. Validate the Apache configuration syntax.
    $ sudo apache2ctl configtest
    Syntax OK

    An AH00558 warning indicates a missing global ServerName and does not prevent the virtual host from working.

  7. Reload apache2 to apply the enabled site.
    $ sudo systemctl reload apache2

    A reload that fails due to syntax errors leaves the previous configuration in place, so changes appear “ignored” until the error is fixed.

  8. Confirm the apache2 service is active.
    $ sudo systemctl status apache2 --no-pager
    ● apache2.service - The Apache HTTP Server
         Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
         Active: active (running) since Sat 2025-12-13 21:05:01 UTC; 7s ago
           Docs: https://httpd.apache.org/docs/2.4/
       Main PID: 1234 (apache2)
          Tasks: 55 (limit: 18751)
         Memory: 6.9M
            CPU: 120ms
         CGroup: /system.slice/apache2.service
                 ├─1234 /usr/sbin/apache2 -k start
    ##### snipped #####
  9. List loaded virtual hosts to confirm the hostname routes to the new config.
    $ sudo apache2ctl -S
    VirtualHost configuration:
    *:80                   is a NameVirtualHost
             default server 000-default (/etc/apache2/sites-enabled/000-default.conf:1)
             port 80 namevhost example.test (/etc/apache2/sites-enabled/example.test.conf:1)
                     alias www.example.test
    ServerRoot: "/etc/apache2"
    ##### snipped #####
  10. Fetch the site locally by forcing the Host header.
    $ curl --include --header 'Host: example.test' http://127.0.0.1/
    HTTP/1.1 200 OK
    Date: Sat, 13 Dec 2025 21:05:22 GMT
    Server: Apache/2.4.58 (Ubuntu)
    Last-Modified: Sat, 13 Dec 2025 21:04:40 GMT
    ETag: "9b-5cfe2c5c3b7b0"
    Accept-Ranges: bytes
    Content-Length: 155
    Content-Type: text/html
    
    <!doctype html>
    ##### snipped #####

    Browser testing without DNS usually requires a hosts entry such as 127.0.0.1 example.test in /etc/hosts.

  11. Check the site logs when routing or permissions do not behave as expected.
    $ sudo tail --lines=3 /var/log/apache2/example.test-access.log
    127.0.0.1 - - [13/Dec/2025:21:05:22 +0000] "GET / HTTP/1.1" 200 155 "-" "curl/8.4.0"
    127.0.0.1 - - [13/Dec/2025:21:05:23 +0000] "GET /favicon.ico HTTP/1.1" 404 487 "-" "Mozilla/5.0"
    ##### snipped #####
Discuss the article:

Comment anonymously. Login not required.