If you’ve ever needed to host more than one website on a single server, you’ve probably run into the term “virtual hosts.” I remember the first time I had to put three client sites on one VPS and panicked a little, thinking I’d need a separate server for each. That’s not the case at all. Apache’s name-based virtual hosting feature was built exactly for this situation, and once you understand it, you’ll wonder why you didn’t set it up sooner.
What Are Virtual Hosts, Really?
A virtual host is Apache’s way of running multiple websites (or “hosts”) on a single machine, using a single IP address and port (usually 80 for HTTP and 443 for HTTPS). Apache figures out which website to serve based on the Host header sent by the browser in the HTTP request.
There are two main types of virtual hosting:
- IP-based virtual hosting: each site gets its own unique IP address.
- Name-based virtual hosting: multiple sites share the same IP address, and Apache distinguishes them using the domain name in the request.
Name-based is by far the more common approach today, mostly because IPv4 addresses are scarce and expensive, and because it’s simply easier to manage. I’ve set up dozens of servers this way, and it scales well whether you’re running two sites or two hundred.
Why Name-Based Virtual Hosting Matters
Here’s why I lean on this feature constantly:
- Cost efficiency: One server, one IP, unlimited domains (within reason).
- Simplified infrastructure: Fewer servers to patch, monitor, and pay for.
- Easier scaling: Adding a new site is a matter of adding a config file, not provisioning new hardware.
- Centralized management: Logs, SSL certificates, and backups can all be handled from one place.
If you’re a freelancer or small agency managing client sites, this is essentially mandatory knowledge. I don’t know how I’d manage my own hosting workflow without it.
Prerequisites
Before diving in, make sure you have:
- A Linux server (I’ll use Ubuntu/Debian syntax, but I’ll note CentOS/RHEL differences where relevant).
- Apache 2.4+ installed (
apache2 -vorhttpd -vto check). - Root or sudo access.
- At least two domain names (or subdomains) pointing to your server’s IP address via DNS A records. If you don’t have real domains yet, you can test locally using entries in your
/etc/hostsfile. - Basic comfort with a terminal text editor like
nanoorvim.
Understanding the Directory Structure
On Debian/Ubuntu systems, Apache’s virtual host configuration lives in:
/etc/apache2/sites-available/
/etc/apache2/sites-enabled/
Files in sites-available are just configuration templates sitting there — they aren’t active until you enable them, which creates a symlink in sites-enabled.
On CentOS/RHEL systems, the structure is a bit different. You’ll typically find configuration in:
/etc/httpd/conf.d/
and there’s no separate “enabled” step — any .conf file dropped in conf.d is automatically loaded.
Step 1: Create a Directory for Each Website
I like to keep things organized by putting each site’s files under /var/www/:
sudo mkdir -p /var/www/example.com/public_html
sudo mkdir -p /var/www/testsite.com/public_html
Assign proper ownership so Apache can read/write as needed:
sudo chown -R $USER:$USER /var/www/example.com/public_html
sudo chown -R $USER:$USER /var/www/testsite.com/public_html
sudo chmod -R 755 /var/www
Now drop a simple test page in each folder so you can verify things later:
echo "<h1>Welcome to Example.com</h1>" > /var/www/example.com/public_html/index.html
echo "<h1>Welcome to Testsite.com</h1>" > /var/www/testsite.com/public_html/index.html
Step 2: Create the Virtual Host Configuration Files
On Debian/Ubuntu, create a new config file for the first domain:
sudo nano /etc/apache2/sites-available/example.com.conf
Here’s a solid baseline configuration I use for most sites:
<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>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Repeat the process for the second domain:
sudo nano /etc/apache2/sites-available/testsite.com.conf
<VirtualHost *:80>
ServerName testsite.com
ServerAlias www.testsite.com
DocumentRoot /var/www/testsite.com/public_html
ErrorLog ${APACHE_LOG_DIR}/testsite.com-error.log
CustomLog ${APACHE_LOG_DIR}/testsite.com-access.log combined
<Directory /var/www/testsite.com/public_html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
A Quick Note on ServerName vs ServerAlias
ServerName is the primary domain Apache matches against. ServerAlias lets you list additional names (like the www variant) that should also route to this same virtual host. I always forget the www alias early in my career and then wondered why the naked domain worked but www.example.com returned the wrong site.
Step 3: Enable the Sites
On Debian/Ubuntu, use the a2ensite helper:
sudo a2ensite example.com.conf
sudo a2ensite testsite.com.conf
It’s also worth disabling the default placeholder site so it doesn’t accidentally catch requests:
sudo a2dissite 000-default.conf
On CentOS/RHEL, there’s no enabling step needed — just make sure your .conf file is in /etc/httpd/conf.d/ and it will be picked up automatically.
Step 4: Test the Configuration and Restart Apache
Always test your syntax before restarting — this has saved me from taking down production sites more times than I’d like to admit:
sudo apache2ctl configtest
If you see Syntax OK, you’re good to go:
sudo systemctl restart apache2
On CentOS/RHEL:
sudo httpd -t
sudo systemctl restart httpd
Step 5: Verify DNS and Test in the Browser
Make sure your DNS A records point example.com and testsite.com to your server’s public IP. DNS propagation can take anywhere from a few minutes to 48 hours, so be patient.
If you want to test locally before DNS propagates, edit your local machine’s hosts file (not the server’s):
- Linux/Mac:
/etc/hosts - Windows:
C:\Windows\System32\drivers\etc\hosts
Add:
203.0.113.10 example.com
203.0.113.10 testsite.com
Then visit http://example.com and http://testsite.com in your browser. You should see the distinct welcome messages you created earlier.
Real-World Use Cases
Over the years I’ve used name-based virtual hosts for:
- Client websites: Hosting a dozen small business sites on one modest VPS.
- Staging and production environments:
staging.example.comandexample.comon the same box during development. - Multi-tenant SaaS-style setups: Where subdomains represent different customer accounts.
- Personal projects: Running a blog, a portfolio, and a side project simultaneously without extra hosting costs.
Adding HTTPS with Virtual Hosts
Once your HTTP virtual hosts work, you’ll want SSL. I strongly recommend Let’s Encrypt via Certbot:
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com
Certbot will automatically detect your existing virtual host, create an HTTPS version, and even set up the redirect from HTTP to HTTPS if you let it.
Troubleshooting Common Issues
Problem: Only one site loads, regardless of domain. This almost always means NameVirtualHost ordering or a missing ServerName directive is confusing Apache, or you still have the default virtual host enabled and it’s catching everything. Disable 000-default.conf and double-check each .conf file has a distinct ServerName.
Problem: “Could not reliably determine the server’s fully qualified domain name” warning. This is a harmless warning tied to the global ServerName directive not being set in apache2.conf or httpd.conf. Add:
ServerName localhost
to the main config file to silence it.
Problem: 403 Forbidden errors. Check your <Directory> block permissions and make sure Require all granted is present for Apache 2.4+. Also verify file system permissions with ls -la /var/www/.
Problem: Changes don’t seem to apply. Did you forget to run a2ensite or restart Apache? This trips up beginners constantly — editing the config file alone does nothing until Apache reloads.
Common Mistakes to Avoid
- Forgetting to disable the default site, causing it to intercept requests meant for your named vhosts.
- Mismatched
DocumentRootpaths that don’t actually contain the files you think they do. - Leaving
AllowOverride Nonewhen you actually need.htaccesssupport for WordPress or similar CMS platforms. - Not testing configuration syntax before restarting Apache in production.
- Forgetting to open port 80/443 in your firewall (
ufw allow 'Apache Full'on Ubuntu).
Security Best Practices
- Disable directory listing with
Options -Indexesunless you specifically want it. - Keep separate log files per virtual host — it makes auditing and debugging dramatically easier.
- Use
Require all granteddeliberately, not as a blanket copy-paste without understanding what it exposes. - Regularly update Apache and your OS packages to patch known vulnerabilities.
- Consider adding security headers (
X-Content-Type-Options,X-Frame-Options, etc.) viamod_headersin each vhost block.
Performance Optimization Tips
- Enable
mod_deflateormod_brotlifor compression across all virtual hosts. - Use a shared cache configuration (see my dedicated post on Apache caching) so every vhost benefits.
- Keep your worker/thread settings tuned appropriately for the number of sites you’re hosting (I cover this in detail in my Apache worker and thread tuning article).
- Monitor per-vhost resource usage with tools like
apachetopor by parsing access logs with GoAccess.
Frequently Asked Questions
Can I run name-based virtual hosts on both port 80 and 443? Yes. You’ll typically create a <VirtualHost *:80> block for HTTP and a matching <VirtualHost *:443> block for HTTPS, both referencing the same ServerName.
How many virtual hosts can one Apache server handle? There’s no hard limit baked into Apache itself. In practice, the limiting factor is your server’s memory, CPU, and disk I/O, not the virtual host mechanism.
Do I need a different IP address for each site? No — that’s the entire point of name-based virtual hosting. All sites can share a single IP address.
What happens if two virtual hosts have the same ServerName? Apache will simply use whichever one it loads first (typically alphabetical order within the enabled sites directory), which can cause confusing bugs. Always keep ServerName values unique.
Can I use wildcard subdomains in a virtual host? Yes, using ServerAlias *.example.com, though you’ll want to be deliberate about which subdomains you actually intend to support.
Summary and Key Takeaways
Name-based virtual hosting is one of the most practical skills you can pick up if you manage your own server. It lets you consolidate multiple websites onto a single machine without sacrificing isolation between them at the configuration level.
The core workflow is always the same:
- Create separate document root directories for each site.
- Write a virtual host configuration block per domain, specifying
ServerName,ServerAlias, andDocumentRoot. - Enable the site and test your configuration syntax.
- Restart Apache and verify with DNS or a local hosts file entry.
- Layer on HTTPS, security headers, and performance tuning once the basics work.
Once you’ve done this a few times, it becomes second nature — I can set up a new virtual host in under two minutes now, including the SSL certificate.