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
sudorights. - Each domain's A record (and
wwwrecord) pointing at your server IP, for example203.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 witha2ensite, which creates a link insites-enabled/. - AlmaLinux and Rocky: files go straight into
/etc/httpd/conf.d/and must end in.conf. There is noa2ensite; 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:
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.htmlGive ownership to the user who deploys the site, and let Apache read it. Here the deploy user is deploy:
sudo chown -R deploy:deploy /var/www/example.com /var/www/example.net
sudo chmod -R 755 /var/www/example.com /var/www/example.netKeeping 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:
<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:
ErrorLog /var/log/httpd/example.com-error.log
CustomLog /var/log/httpd/example.com-access.log combinedWhat each line does:
ServerNameis the main domain. Use exactly one per vhost.ServerAliaslists other names for the same site, separated by spaces, such aswww.example.com.DocumentRootis the folder Apache serves.- The
<Directory>block grants access (Require all granted), hides folder listings (-Indexes) and allows.htaccessfiles (AllowOverride All). UseAllowOverride Noneif you never use.htaccess; it is slightly faster. ErrorLogandCustomLoggive each site its own logs, which makes debugging much easier.
Test, enable and reload
Always check syntax first:
sudo apachectl configtestYou want Syntax OK. On Ubuntu and Debian, enable both sites, disable the stock default site and reload:
sudo a2ensite example.com.conf example.net.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2On AlmaLinux and Rocky, just reload:
sudo systemctl reload httpdTo 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:
sudo apachectl -SEach domain should appear with its config file and line number. Then request each site by name from the server:
curl -H "Host: example.com" http://127.0.0.1
curl -H "Host: example.net" http://127.0.0.1Each 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
ServerNameorServerAlias, so it falls through to the first vhost. Check the output ofapachectl -S. - 403 Forbidden: the
<Directory>block is missingRequire 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. a2ensitesays the site does not exist: the file name must end in.confand live insites-available.- The Apache default page still appears:
000-default.confis still enabled, or your domain is not listed. Disable the default site and reload. - Reload fails: run
sudo apachectl configtestand fix the line it reports. A typo such as an unclosed<Directory>block stops every site.
Next steps
- Add free HTTPS to each domain: Secure Apache with Let's Encrypt.
- Run PHP sites efficiently: PHP-FPM with Apache using proxy_fcgi.
