Websites & apps

Set up Apache virtual hosts for multiple websites

Host several websites on one Apache server with name-based virtual hosts. One config file per domain, its own document root and its own logs, on Ubuntu, Debian, AlmaLinux and Rocky Linux.

BeginnerLinux server5 min read

A virtual host tells Apache which folder to serve for which domain name. With one file per domain you can run many websites on a single VPS, each with its own document root and its own logs. This guide sets up two sites, example.com and example.net, and shows how to test and switch them on safely.

Before you start

  • Apache installed and running. On AlmaLinux or Rocky, see Install Apache on AlmaLinux and Rocky Linux.
  • A user with sudo rights.
  • Each domain's A record (and www record) pointing at your server IP, for example 203.0.113.10. You can build the vhosts before DNS is ready and test with a hosts-file entry.

How Apache picks a site

When a request arrives on port 80, Apache reads the Host header the browser sends and compares it with the ServerName and ServerAlias of each <VirtualHost *:80> block. The first match wins. If nothing matches, Apache uses the first virtual host it loaded for that port. That is why visitors sometimes see the wrong site: a domain was not listed anywhere, so it fell through to the default.

The file layout differs by distribution:

  • Ubuntu and Debian: files go in /etc/apache2/sites-available/. You enable them with a2ensite, which creates a link in sites-enabled/.
  • AlmaLinux and Rocky: files go straight into /etc/httpd/conf.d/ and must end in .conf. There is no a2ensite; a file is active as soon as it is there.

Create the document roots

Make a folder for each site and a simple test page:

Bash
sudo mkdir -p /var/www/example.com/public /var/www/example.net/public
echo 'example.com works' | sudo tee /var/www/example.com/public/index.html
echo 'example.net works' | sudo tee /var/www/example.net/public/index.html

Give ownership to the user who deploys the site, and let Apache read it. Here the deploy user is deploy:

Bash
sudo chown -R deploy:deploy /var/www/example.com /var/www/example.net
sudo chmod -R 755 /var/www/example.com /var/www/example.net

Keeping the web root in a public subfolder means config files and backups placed one level up are never served.

Write one config file per domain

Ubuntu and Debian

Create /etc/apache2/sites-available/example.com.conf:

Apache
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin admin@example.com
    DocumentRoot /var/www/example.com/public

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

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

Copy it to example.net.conf and change every example.com to example.net.

AlmaLinux and Rocky Linux

Create /etc/httpd/conf.d/example.com.conf with the same block, but write the log paths out in full, because APACHE_LOG_DIR is a Debian variable:

Apache
    ErrorLog /var/log/httpd/example.com-error.log
    CustomLog /var/log/httpd/example.com-access.log combined

What each line does:

  • ServerName is the main domain. Use exactly one per vhost.
  • ServerAlias lists other names for the same site, separated by spaces, such as www.example.com.
  • DocumentRoot is the folder Apache serves.
  • The <Directory> block grants access (Require all granted), hides folder listings (-Indexes) and allows .htaccess files (AllowOverride All). Use AllowOverride None if you never use .htaccess; it is slightly faster.
  • ErrorLog and CustomLog give each site its own logs, which makes debugging much easier.

Test, enable and reload

Always check syntax first:

Bash
sudo apachectl configtest

You want Syntax OK. On Ubuntu and Debian, enable both sites, disable the stock default site and reload:

Bash
sudo a2ensite example.com.conf example.net.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2

On AlmaLinux and Rocky, just reload:

Bash
sudo systemctl reload httpd

To take a site offline later, run sudo a2dissite example.net.conf (Debian family) or rename the file to example.net.conf.disabled (RHEL family), then reload.

Tip: Keep a catch-all default site that returns a plain page or 404. Name it 000-default.conf so it loads first. Unknown domains pointed at your IP then never show one of your real sites.

Check it works

List the virtual hosts Apache has loaded:

Bash
sudo apachectl -S

Each domain should appear with its config file and line number. Then request each site by name from the server:

Bash
curl -H "Host: example.com" http://127.0.0.1
curl -H "Host: example.net" http://127.0.0.1

Each command should print its own test line. If DNS is not live yet, add a line such as 203.0.113.10 example.com www.example.com to the hosts file on your own computer and open the site in a browser.

Troubleshooting

  • Every domain shows the same site: the domain is missing from ServerName or ServerAlias, so it falls through to the first vhost. Check the output of apachectl -S.
  • 403 Forbidden: the <Directory> block is missing Require all granted, a parent folder is not readable, or, on AlmaLinux and Rocky, the folder has the wrong SELinux label. See Fix 403 Forbidden errors in Apache.
  • a2ensite says the site does not exist: the file name must end in .conf and live in sites-available.
  • The Apache default page still appears: 000-default.conf is still enabled, or your domain is not listed. Disable the default site and reload.
  • Reload fails: run sudo apachectl configtest and fix the line it reports. A typo such as an unclosed <Directory> block stops every site.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.