Creating a separate Apache virtual host keeps each site on its own hostname, DocumentRoot, and log files, which makes multi-site hosting predictable and makes it much easier to see which configuration is serving a request.
On a shared listener such as `<VirtualHost *:80>`, Apache first chooses the best IP:port match, then searches the enabled virtual hosts in order for the first matching ServerName or ServerAlias from the request's Host header. If no name matches, the first enabled virtual host for that address and port becomes the fallback, which is why 000-default.conf often catches unknown hostnames until a different default is arranged.
This guide uses the packaged apache2 layout on Ubuntu and Debian, where site files live in /etc/apache2/sites-available and a2ensite creates the symlink in /etc/apache2/sites-enabled. It assumes Apache is already installed and listening on port 80; on RHEL-style systems the file path is usually /etc/httpd/conf.d and the service name is httpd, while the same ServerName, ServerAlias, DocumentRoot, and `<Directory>` concepts still apply. Run a syntax test before reloading, and keep ServerName explicit so startup warnings and fallback matches stay predictable.
Related: How to test Apache configuration
Related: How to enable or disable Apache modules
Related: Location for Apache VirtualHost configuration
Steps to create a virtual host in Apache:
- Pick the site hostname and web root path.
Example hostname: app.internal.example
Example document root: /var/www/app.internal.example/public - Create the document root directory with readable permissions.
$ sudo install --directory --mode=0755 /var/www/app.internal.example/public
- Write a simple page into the new document root so the final request test has a distinctive response.
$ sudo tee /var/www/app.internal.example/public/index.html >/dev/null <<'EOF' <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>app.internal.example</title> </head> <body> <h1>app.internal.example virtual host works</h1> </body> </html> EOF - Create the site configuration file in /etc/apache2/sites-available.
$ sudo tee /etc/apache2/sites-available/app.internal.example.conf >/dev/null <<'EOF' <VirtualHost *:80> ServerName app.internal.example ServerAlias www.app.internal.example DocumentRoot /var/www/app.internal.example/public <Directory /var/www/app.internal.example/public> Options -Indexes +FollowSymLinks AllowOverride None Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/app.internal.example-error.log CustomLog ${APACHE_LOG_DIR}/app.internal.example-access.log combined </VirtualHost> EOFKeep the actual hostname in ServerName and ServerAlias, not in the `<VirtualHost>` address. The wildcard listener stays *:80, and Apache uses the request hostname to select the matching virtual host.
AllowOverride None keeps configuration in the virtual host file and stops Apache from reading .htaccess files in that path. Change it only when the application really requires .htaccess overrides.
- Enable the new site.
$ sudo a2ensite app.internal.example.conf Enabling site app.internal.example. To activate the new configuration, you need to run: service apache2 reload
On current Debian-based packages, a2ensite works by creating symlinks under /etc/apache2/sites-enabled.
000-default.conf remains the fallback virtual host for unknown names until you disable it or replace it with another first-loaded catch-all site.
- Test the configuration before reloading Apache.
$ sudo apache2ctl configtest AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 192.0.2.10. Set the 'ServerName' directive globally to suppress this message Syntax OK
The AH00558 line is a global ServerName warning, not a virtual-host syntax failure. Syntax OK means the new site file parsed successfully.
- Reload the service so the enabled site becomes active.
$ sudo systemctl reload apache2
The helper text from a2ensite still prints service apache2 reload on current Debian and Ubuntu packages, but systemctl reload apache2 is the normal equivalent on systemd hosts.
- List the loaded virtual hosts to confirm the new hostname is mapped to the expected file.
$ sudo apache2ctl -S VirtualHost configuration: *:80 is a NameVirtualHost default server 192.0.2.10 (/etc/apache2/sites-enabled/000-default.conf:1) port 80 namevhost 192.0.2.10 (/etc/apache2/sites-enabled/000-default.conf:1) port 80 namevhost app.internal.example (/etc/apache2/sites-enabled/app.internal.example.conf:1) alias www.app.internal.example ServerRoot: "/etc/apache2" Main DocumentRoot: "/var/www/html" ##### snipped #####If the new hostname is missing here, the site file is still disabled or the syntax test is still failing.
- Send a local request with the target Host header so Apache selects the new virtual host without waiting for public DNS.
$ curl --silent --include --header 'Host: app.internal.example' http://127.0.0.1/ HTTP/1.1 200 OK Date: Wed, 08 Apr 2026 04:37:11 GMT Server: Apache/2.4.58 (Ubuntu) Last-Modified: Wed, 08 Apr 2026 04:37:09 GMT ETag: "c7-64eeb73f0d3ea" Accept-Ranges: bytes Content-Length: 199 Vary: Accept-Encoding Content-Type: text/html <!doctype html> <html lang="en"> ##### snipped #####
If DNS already points at the server, request the real URL directly instead. For browser testing before DNS is live, add a temporary hosts entry such as 127.0.0.1 app.internal.example on the test machine.
Related: Override Host header in cURL
- Inspect the site-specific access log if the wrong page, a 403 Forbidden response, or an unexpected redirect still appears.
$ sudo tail --lines=3 /var/log/apache2/app.internal.example-access.log 127.0.0.1 - - [08/Apr/2026:04:37:11 +0000] "GET / HTTP/1.1" 200 450 "-" "curl/8.5.0" ##### snipped #####
A hit in the access log confirms the request reached this virtual host. A missing hit usually means the request matched a different hostname, a different listener, or a different proxy layer first.
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.
