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.
Related: How to test your Apache configuration
Related: How to enable or disable Apache modules
Steps to create a virtual host in Apache:
- Select the hostname for the new site.
Example hostname: example.test
Example document root: /var/www/example.test/public - Create the document root directory.
$ sudo mkdir --parents /var/www/example.test/public
- 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 - 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> EOFAllowOverride None disables .htaccess processing for performance and clarity; switch to AllowOverride All only when an application requires it.
- 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.
- 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.
- 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.
- 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 ##### - 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 ##### - 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.
- 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 #####
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
Comment anonymously. Login not required.
