Skip to content
DevOps devops webservers 5 min read

Apache Virtual Hosts

A single Apache server can host many websites at once. The feature that makes this possible is called a virtual host (often shortened to vhost) — a block of configuration that tells Apache “when a request comes in for this domain name, serve files from this folder.” Without virtual hosts you would need one server per site, which is slow and expensive. In this page you will create a real virtual host on Ubuntu, enable it, turn off the default site, and reload Apache so your new site goes live.

This guide is the Apache equivalent of the Nginx “server blocks” page. The idea is identical; only the file layout and commands differ.

How Apache organises sites on Ubuntu

On Ubuntu (22.04 / 24.04 LTS), the Apache package (apache2) ships with a tidy two-folder system so you can keep many site configs around but only switch on the ones you want.

FolderWhat it holds
/etc/apache2/sites-available/All site config files (.conf), enabled or not. This is where you write them.
/etc/apache2/sites-enabled/Symbolic links (shortcuts) pointing back to files in sites-available. Apache only reads these.

When to use this layout: always. Keeping every config in sites-available and only linking the active ones into sites-enabled means you can disable a site instantly without deleting its config. Never edit files directly inside sites-enabled — edit the real file in sites-available, because the link just mirrors it.

You never create the links by hand. Two helper commands do it for you:

  • a2ensiteapache2 enable site (creates the link).
  • a2dissiteapache2 disable site (removes the link).

Step 1: Create the document root and a test page

The document root is the folder on disk where your website’s files live. Let’s host a site for example.test and give it its own folder.

sudo mkdir -p /var/www/example.test/public_html
sudo chown -R $USER:$USER /var/www/example.test/public_html
echo "<h1>Hello from example.test</h1>" | sudo tee /var/www/example.test/public_html/index.html

The chown line (change owner) hands the folder to your user so you don’t need sudo for every edit. $USER is automatically your login name.

Step 2: Write the virtual host config

Create a .conf file in sites-available. The filename should match the domain so it’s easy to find later.

sudo nano /etc/apache2/sites-available/example.test.conf

Paste this configuration:

<VirtualHost *:80>
    ServerName example.test
    ServerAlias www.example.test
    DocumentRoot /var/www/example.test/public_html

    <Directory /var/www/example.test/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/example.test-error.log
    CustomLog ${APACHE_LOG_DIR}/example.test-access.log combined
</VirtualHost>

What each line does:

  • <VirtualHost *:80> — this block handles requests arriving on port 80 (plain HTTP) for any IP address (*). Apache decides which block to use by matching ServerName.
  • ServerName — the main domain this site answers to.
  • ServerAlias — extra names that also map here (so www.example.test works too).
  • DocumentRoot — the folder Apache serves files from.
  • <Directory> — access rules for that folder. Require all granted allows the public to read it; AllowOverride All lets .htaccess files override settings; Options -Indexes hides the file listing when there’s no index.html (a small security win).
  • ErrorLog / CustomLog — per-site log files. ${APACHE_LOG_DIR} expands to /var/log/apache2.

Step 3: Enable the site and disable the default

Ubuntu ships with a default site (000-default.conf) that catches every request. Turn it off so your new site responds first, then enable your site.

sudo a2ensite example.test.conf
sudo a2dissite 000-default.conf

Output:

Enabling site example.test.
To activate the new configuration, you need to run:
  systemctl reload apache2
Site 000-default disabled.
To activate the new configuration, you need to run:
  systemctl reload apache2

These commands only create and remove the symlinks in sites-enabled. Nothing is live until you reload Apache.

Step 4: Test the config and reload

Always check your syntax before reloading — a typo can take every site on the box down.

sudo apache2ctl configtest

Output:

Syntax OK

If you see Syntax OK, apply the change:

sudo systemctl reload apache2

reload re-reads the config without dropping live connections, unlike restart which fully stops and starts the service.

Step 5: Verify it works

If you don’t own the domain yet, fake the DNS lookup by adding a line to your local /etc/hosts file so example.test points at the server.

echo "127.0.0.1 example.test" | sudo tee -a /etc/hosts
curl -H "Host: example.test" http://127.0.0.1

Output:

<h1>Hello from example.test</h1>

The -H "Host: example.test" header tells Apache which virtual host you want, exactly as a browser would. To repeat this for a second site, copy the four steps with a new folder, domain, and .conf file.

a2ensite vs editing sites-enabled directly

ApproachWhen to useRisk
a2ensite / a2dissiteAlways — the supported wayNone; it’s reversible
Manually creating symlinksScripted/automated provisioning onlyEasy to typo the path
Editing files in sites-enabledNeverYou’re editing a link target; confusing and easy to lose work

Gotcha: the order Apache evaluates virtual hosts matters. If no ServerName matches the incoming request, Apache falls back to the first enabled vhost (alphabetically by filename). That’s why Ubuntu prefixes its default with 000- — keep that in mind when a request lands on the wrong site.

Best Practices

  • Name each config file after its domain (example.test.conf) so sites-available stays readable.
  • Always run sudo apache2ctl configtest before reloading — one bad block breaks all sites.
  • Give every vhost its own ErrorLog and CustomLog so you can debug one site without noise from others.
  • Disable 000-default.conf in production so unmatched requests don’t leak the default Ubuntu page.
  • Use reload, not restart, for config changes to avoid dropping active connections.
  • Add HTTPS later with a separate <VirtualHost *:443> block (Certbot can generate it for you).
Last updated June 15, 2026
Was this helpful?