Table of Contents
Why Virtual Hosts Matter
Virtual hosts let a single web server (one IP/port combination) serve multiple websites with different domain names and/or configurations. Instead of running a separate server process per site, you define multiple “virtual” servers inside one Apache or Nginx instance.
You typically use virtual hosts to:
- Host multiple domains:
example.com,shop.example.com,another-site.net - Separate HTTP and HTTPS configurations per site
- Apply different document roots, logging, security, and PHP/CGI settings per site
- Serve different content based on hostname or URL path
Two dominant types:
- Name-based virtual hosts – Multiple domains on the same IP/port, distinguished by
Hostheader - IP-based virtual hosts – Different IPs per site, less common today, used for special cases
This chapter focuses on how to define and manage virtual hosts in Apache and Nginx.
Name-Based vs IP-Based Virtual Hosts
Name-Based Virtual Hosts
Most common scenario:
- One IP:
203.0.113.10 - Multiple hostnames:
example.com,blog.example.com,another.com - The browser sends the
Host: example.comheader - The web server chooses the matching virtual host block
Requirements:
- DNS A/AAAA records for each hostname pointing to the same IP
- Web server configured with multiple virtual host definitions on that IP/port
Limitations:
- Very old HTTPS constraints used to require dedicated IPs per SSL cert; this is mostly solved with SNI (Server Name Indication). Some very old clients lack SNI support, but in modern deployments name-based SSL vhosts are standard.
IP-Based Virtual Hosts
Each virtual host has its own IP address:
192.0.2.10→site1.com192.0.2.11→site2.com
Used when:
- You must support legacy, non-SNI HTTPS clients
- You need routing/ACLs based on IP upstream (e.g., load balancers, firewalls)
- Specific compliance or organizational requirements demand IP separation
Trade-offs:
- Uses more public IP addresses
- More complex network/DNS configuration
Apache Virtual Hosts
Apache uses <VirtualHost> blocks. The parent “Apache basics” chapter covers modules, configuration file locations, and reloading the service; here we focus on vhost specifics.
Core Concepts
- Each
<VirtualHost>block defines one site (or closely related set of hostnames) - Apache selects the best matching vhost based on:
- IP and port (
<VirtualHost *:80>vs<VirtualHost 192.0.2.10:80>) ServerNameandServerAlias- The first matching vhost for a given IP:port acts as the default for that combination
Minimal Name-Based Virtual Host (HTTP)
Example for two HTTP sites on the same host:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example
ErrorLog ${APACHE_LOG_DIR}/example_error.log
CustomLog ${APACHE_LOG_DIR}/example_access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName blog.example.com
DocumentRoot /var/www/blog
ErrorLog ${APACHE_LOG_DIR}/blog_error.log
CustomLog ${APACHE_LOG_DIR}/blog_access.log combined
</VirtualHost>Key directives:
ServerName– Canonical hostname this vhost responds toServerAlias– Additional hostnames, including wildcards like*.example.comDocumentRoot– Directory to serve files fromErrorLog,CustomLog– Per-site logging
Default Virtual Host
If a request comes to an unknown hostname on :80, Apache serves the first vhost defined for :80. You can explicitly create a “catch-all” vhost:
<VirtualHost *:80>
ServerName default.example.local
DocumentRoot /var/www/default
</VirtualHost>Order in the configuration files matters for the default.
IP-Based Virtual Host in Apache
Assign different IP addresses and bind vhosts to them:
# Listen on two IPs (if not already implicit)
Listen 192.0.2.10:80
Listen 192.0.2.11:80
<VirtualHost 192.0.2.10:80>
ServerName site1.com
DocumentRoot /var/www/site1
</VirtualHost>
<VirtualHost 192.0.2.11:80>
ServerName site2.com
DocumentRoot /var/www/site2
</VirtualHost>Ensure:
- The OS has both IPs configured
- DNS maps each hostname to the correct IP
HTTPS Virtual Hosts (SNI) in Apache
Multiple HTTPS sites on the same IP/port with SNI:
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/example
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.crt
SSLCertificateKeyFile /etc/ssl/private/example.key
</VirtualHost>
<VirtualHost *:443>
ServerName blog.example.com
DocumentRoot /var/www/blog
SSLEngine on
SSLCertificateFile /etc/ssl/certs/blog.crt
SSLCertificateKeyFile /etc/ssl/private/blog.key
</VirtualHost>Notes:
- SNI lets the client include the hostname during the TLS handshake
- Apache chooses the vhost (and certificate) based on that name
- For very old clients without SNI, Apache will serve the certificate from the default SSL vhost
Per-Virtual-Host Overrides
Virtual hosts allow per-site customizations without affecting others:
Examples:
<VirtualHost *:80>
ServerName app.example.com
DocumentRoot /var/www/app/public
# PHP handler or proxy config
ProxyPassMatch ^/(.*\.php)$ fcgi://127.0.0.1:9000/var/www/app/public/$1
# Restrict admin area by IP
<Location "/admin">
Require ip 203.0.113.0/24
</Location>
# Separate log format for this app
CustomLog ${APACHE_LOG_DIR}/app_access.log vhost_combined
</VirtualHost>Typical per-vhost tweaks:
- Security headers
- Directory permissions (
<Directory>) andAllowOverride - Proxying to application backends
- Rate limiting and request size limits
Nginx Server Blocks (Virtual Hosts)
In Nginx, the equivalent of a virtual host is a server block inside the http context. The parent “Nginx basics” chapter covers overall layout and reloading; this chapter focuses on virtual host features.
Core Concepts
- Each
serverblock defines one virtual host listen+server_namedetermine which requests match- The first server for a given
listencan act as the default unlessdefault_serveris specified
Minimal Name-Based Virtual Host (HTTP)
Example of two sites:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example;
index index.html index.htm;
access_log /var/log/nginx/example_access.log;
error_log /var/log/nginx/example_error.log;
}
server {
listen 80;
server_name blog.example.com;
root /var/www/blog;
index index.html;
access_log /var/log/nginx/blog_access.log;
error_log /var/log/nginx/blog_error.log;
}Key directives:
listen– IP/port/flags (e.g.,listen 80;,listen 192.0.2.10:80;)server_name– Hostnames/wildcards (e.g.,example.com,*.example.com,_as catch-all)root– Document root, often combined withlocationblocks
Default Server
Specify default_server on the listen line:
server {
listen 80 default_server;
server_name _;
root /var/www/default;
}
This block handles requests that don’t match any server_name for listen 80.
Wildcards and Regex in `server_name`
Nginx supports:
- Exact names:
server_name example.com; - Wildcards:
server_name *.example.com;(matchesa.example.com,b.example.com)server_name example.*;- Regular expressions:
server {
listen 80;
server_name ~^(?<sub>.+)\.example\.com$;
# $sub holds the captured subdomain
}Regex server names are advanced and evaluated after exact/wildcard matches; use only when necessary.
IP-Based Virtual Hosts in Nginx
Bind servers to specific IPs:
server {
listen 192.0.2.10:80;
server_name site1.com;
root /var/www/site1;
}
server {
listen 192.0.2.11:80;
server_name site2.com;
root /var/www/site2;
}Again, IP addresses must be configured at the OS level and in DNS.
HTTPS Virtual Hosts (SNI) in Nginx
Multiple SSL sites on one IP using SNI:
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/example;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
}
server {
listen 443 ssl http2;
server_name blog.example.com;
root /var/www/blog;
ssl_certificate /etc/ssl/certs/blog.crt;
ssl_certificate_key /etc/ssl/private/blog.key;
}Nginx chooses the certificate based on the SNI hostname. You can also use a single multi-SAN or wildcard certificate across several vhosts if that suits your naming scheme.
Mixed HTTP/HTTPS Vhosts with Redirect
Common pattern: one vhost for HTTP redirect, one for HTTPS content:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/example;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
}Multi-Site Management Patterns
Directory Layouts
Typical per-site layout:
- Apache:
- Config:
/etc/apache2/sites-available/example.conf - Docroot:
/var/www/example/public - Logs:
/var/log/apache2/example_{access,error}.log - Nginx:
- Config:
/etc/nginx/sites-available/example - Docroot:
/var/www/example - Logs:
/var/log/nginx/example_{access,error}.log
Using a consistent layout makes automation and troubleshooting much easier.
Per-Site Security and Isolation
Virtual hosts share the same server process, so they are not fully isolated. You can improve separation per vhost:
- Separate Unix users and groups owning each site’s files
- Apache:
- Use
Suexec,mpm_itk, or PHP-FPM pools with different users per vhost - Nginx:
- Separate PHP-FPM pools per site (different Unix users)
- Limit writable directories per vhost to reduce compromise impact
For stronger isolation, consider containers or separate VMs per site, combining them with virtual hosts as needed.
Logging and Monitoring
Per-vhost logs are central to server administration:
- Easier to:
- Analyze traffic per site
- Detect attacks focused on a specific domain
- Track performance and errors independently
- Integrate logs with central logging systems, using different log formats or tags per virtual host if needed
Troubleshooting Virtual Host Issues
Common problems and how to reason about them:
- Wrong site is served
- Check which vhost is the default for that IP/port
- Confirm the
Hostheader (e.g., withcurl -H 'Host: example.com' http://IP/) - Verify
server_name/ServerNameandServerAliasvalues - HTTPS certificate mismatch
- Ensure the requested hostname matches the vhost’s certificate
- Confirm that the correct vhost is the default for
443and SNI is enabled - Test with
openssl s_client -connect host:443 -servername example.com - New virtual host not taking effect
- Check that its config file is included (Apache
sites-enabled, Nginxincludedirectives) - Validate configuration syntax before reload (e.g. Apache/Nginx test commands)
- Reload or restart the service
- 404 or 403 when accessing a new site
- Confirm
DocumentRoot/rootpath exists and permissions are correct - Check per-vhost access rules and path mappings
Understanding how the server chooses a virtual host (IP/port + host name + default server behavior) is the key to systematically debugging these issues.