Overview
Apache virtual hosts let you run multiple websites on a single server using one IP address. Instead of spinning up a separate machine for every domain, Apache reads the incoming HTTP request’s Host header and routes traffic to the correct document root. That’s the whole mechanism.
You’ll need this if you’re self-managing a VPS SSD Hosting plan and want to host more than one site without paying for separate servers. It’s also the standard setup on any bare Linux box where you’re not using a control panel like cPanel or Plesk to abstract this away for you.
This guide covers name-based virtual hosting on Apache 2.4 running on Ubuntu 22.04 or 24.04. The config structure is nearly identical on AlmaLinux 8/9 and Debian 12 — I’ll call out the differences where they matter.
Prerequisites
- Root or sudo access to your server
- Apache 2.4 installed and running (
apache2on Ubuntu/Debian,httpdon AlmaLinux/CentOS) - A domain name with DNS A records pointing to your server’s public IP
- DNS propagation complete — allow up to 24 hours, though most records resolve within 1-2 hours
- Basic comfort with the Linux command line and a terminal text editor (nano is fine)
Step-by-Step Instructions
Step 1: Create the Document Root Directories
Each site needs its own directory where its files will live. The conventional location on Ubuntu is /var/www/. Create one folder per domain:
sudo mkdir -p /var/www/example.com/public_html
sudo mkdir -p /var/www/secondsite.com/public_html
Now set ownership so Apache (and your user account) can read and write to those directories:
sudo chown -R $USER:$USER /var/www/example.com/public_html
sudo chown -R $USER:$USER /var/www/secondsite.com/public_html
sudo chmod -R 755 /var/www
Drop a quick test page in each so you can confirm routing works later:
echo '<h1>example.com is working</h1>' > /var/www/example.com/public_html/index.html
echo '<h1>secondsite.com is working</h1>' > /var/www/secondsite.com/public_html/index.html
Step 2: Create the Virtual Host Config Files
On Ubuntu and Debian, virtual host configs live in /etc/apache2/sites-available/. Each site gets its own .conf file. This separation makes it easy to enable or disable sites without editing a monolithic config.
Create the config for your first domain:
sudo nano /etc/apache2/sites-available/example.com.conf
Paste in the following, substituting your actual domain and paths:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
<Directory /var/www/example.com/public_html>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Repeat the same process for your second domain:
sudo nano /etc/apache2/sites-available/secondsite.com.conf
<VirtualHost *:80>
ServerName secondsite.com
ServerAlias www.secondsite.com
DocumentRoot /var/www/secondsite.com/public_html
ErrorLog ${APACHE_LOG_DIR}/secondsite.com-error.log
CustomLog ${APACHE_LOG_DIR}/secondsite.com-access.log combined
<Directory /var/www/secondsite.com/public_html>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
📝 Note: On AlmaLinux or CentOS with httpd, there’s no sites-available / sites-enabled split. You’d place these configs directly in /etc/httpd/conf.d/ and they’re automatically loaded. Skip Steps 3 and 4 and go straight to the syntax check.
Step 3: Enable the Virtual Host Configs
Ubuntu’s Apache uses symlinks to activate configs. The a2ensite command creates those symlinks from sites-available into sites-enabled for you:
sudo a2ensite example.com.conf
sudo a2ensite secondsite.com.conf
If the default Apache placeholder page is still enabled, disable it so it doesn’t intercept requests:
sudo a2dissite 000-default.conf
⚠ Warning: Leaving 000-default.conf enabled is a very common mistake. Apache processes virtual hosts in alphabetical order, and 000-default sorts first. If it’s still active, it’ll catch requests that don’t match any other vhost — including yours during DNS propagation — and serve the Apache default page instead of your site.
Step 4: Test the Config and Reload Apache
Before reloading, always run a syntax check. This catches typos that would bring down all sites on the server, not just the one you edited:
sudo apache2ctl configtest
You’re looking for Syntax OK at the end of the output. If you see errors, fix them before proceeding.
Once the check passes, reload Apache to apply the new configs without dropping active connections:
sudo systemctl reload apache2
📝 Note: Use reload here, not restart. A reload sends a graceful signal that lets existing connections finish. A restart terminates everything immediately — that matters on a live server.
Step 5: Verify the Sites Are Working
Open a browser and visit http://example.com and http://secondsite.com. You should see the test pages you created in Step 1.
If DNS hasn’t propagated yet and you want to test locally, you can fake it by adding entries to your local /etc/hosts file (on Mac or Linux) or C:WindowsSystem32driversetchosts on Windows:
YOUR_SERVER_IP example.com www.example.com
YOUR_SERVER_IP secondsite.com www.secondsite.com
Remove those lines once DNS is properly set up. Forgetting to clean up /etc/hosts will make you think DNS is working when it’s actually still routing locally — I’ve wasted an embarrassing amount of time on that one.
Step 6: Add SSL (Recommended)
Serving sites over plain HTTP in 2026 will get you a browser warning and a rankings penalty. The fastest path to SSL on a self-managed server is Certbot with the Let’s Encrypt CA:
sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d example.com -d www.example.com
Certbot will modify your virtual host config to add port 443 blocks and handle the HTTP-to-HTTPS redirect automatically. Run the same command again for secondsite.com.
Common Issues & Troubleshooting
Apache Serves the Wrong Site or the Default Page
Almost always caused by 000-default.conf still being enabled, or a missing/wrong ServerName directive. Double-check that ServerName exactly matches the domain you’re requesting (no trailing slash, no typos). Run sudo apache2ctl -S to see which virtual host Apache thinks owns each domain — it prints the full vhost mapping to stdout, which is much faster than guessing.
403 Forbidden on the Document Root
This usually means a permissions problem on the directory. Apache’s worker process runs as www-data on Ubuntu. It needs at least execute (x) permission on every directory in the path and read (r) on the files. The chmod 755 from Step 1 handles the web root itself, but if your document root is nested somewhere unusual (like inside a user’s home directory), every parent directory needs x for www-data too. Also confirm your <Directory> block has Require all granted.
Changes to the Config File Aren’t Taking Effect
You edited the file in sites-available but forgot that the active file is a symlink in sites-enabled. The symlink points to the original, so edits do propagate — but you still need to reload Apache. If you ran systemctl reload apache2 and it didn’t help, check that the configtest passes and look at the error log: sudo tail -f /var/log/apache2/example.com-error.log.
ServerName Mismatch Warning in Error Logs
You’ll see something like: AH00558: apache2: Could not reliably determine the server's fully qualified domain name. This is harmless but annoying. Fix it by setting a global ServerName in /etc/apache2/apache2.conf:
ServerName localhost
Then reload. The warning disappears. It doesn’t affect how your virtual hosts work.
Certbot Fails with “Could Not Automatically Find a Matching Server Block”
Certbot’s Apache plugin finds your vhost by matching the -d domain against ServerName directives. If your ServerName is missing or uses a different capitalisation, it won’t find the block. Confirm the exact string in your .conf file matches what you’re passing to certbot -d. Also make sure port 80 is open in your firewall — Certbot needs to complete an HTTP-01 challenge before it’ll issue the certificate.
FAQ
Frequently Asked Questions
How many virtual hosts can Apache handle on one server?
There’s no hard limit built into Apache itself. In practice, it’s constrained by your server’s RAM, CPU, and available file descriptors. A modest VPS with 2GB RAM can comfortably handle dozens of low-traffic sites. High-traffic sites need more resources per vhost, so plan accordingly.
Do I need a separate IP address for each virtual host?
No. Name-based virtual hosting — which is what this guide sets up — lets multiple domains share one IP address. Apache reads the HTTP Host header to figure out which site to serve. You’d only need separate IPs for IP-based virtual hosting, which is essentially obsolete now that SNI handles SSL on shared IPs.
What's the difference between sites-available and sites-enabled in Apache?
sites-available holds all your config files, active or not. sites-enabled holds symlinks to whichever configs you’ve actually activated with a2ensite. It’s a clean way to stage configs and disable a site without deleting its config file. On RedHat-based systems like AlmaLinux, this split doesn’t exist — all .conf files in /etc/httpd/conf.d/ are loaded automatically.
Can I use Apache virtual hosts with WordPress?
Yes, and it’s a common setup. You’ll want AllowOverride All in your Directory block so WordPress’s .htaccess file can handle pretty permalinks. Without it, all URLs except the homepage return a 404. If you’d rather not manage this yourself, Host & Tech’s managed WordPress hosting handles the server config for you.
How do I test a virtual host before DNS is pointed at my server?
Edit your local machine’s /etc/hosts file (or C:WindowsSystem32driversetchosts on Windows) and add a line mapping your server’s IP to the domain. This overrides DNS resolution on your machine only. Just remember to remove those lines once real DNS is working, or you’ll keep bypassing the actual DNS records without realising it.