LFCE 52 ๐ง Apache โ Virtual Hosts
Apache virtual hosts are the mechanism that allows a single server to serve multiple domains. The previous chapter covered the basic configuration: the ServerName, the DocumentRoot, and the <Directory> block that grants access to the filesystem. A virtual host is a <VirtualHost> container that groups those directives and associates them with a specific address and port. When a request arrives, Apache reads the Host header and selects the virtual host whose ServerName or ServerAlias matches. If no host matches, the first virtual host for that address and port handles the request.
The configuration syntax differs slightly between Debian and RHEL in the file layout and the enabling mechanism, but the <VirtualHost> container is the same on both. The previous chapter covered the Nginx equivalent; this chapter covers the Apache implementation, including the name-based virtual host, the ServerAlias directive, the default host selection, and the validation commands that confirm the configuration is correct before the reload.
Key point: A name-based virtual host is selected by matching the request’s Host header against the ServerName and ServerAlias directives. The ServerName is the primary hostname, and the ServerAlias lists additional hostnames that the same virtual host should handle. If no virtual host matches, Apache uses the first virtual host defined for the address and port, which is why the order of the <VirtualHost> blocks matters. Always run apache2ctl configtest (Debian) or httpd -t (RHEL) before reloading.
Why virtual hosts require understanding name-based selection
The name-based selection problem.
A single IP address can host many domains. Apache distinguishes them by the Host header that the client sends with the request. The <VirtualHost> block with a matching ServerName or ServerAlias handles the request. If no block matches, Apache falls back to the first block for that address and port. The fallback is not an error; it is the default behavior, and it is why the order of the virtual host definitions matters. The first block is the default for its address and port, regardless of its content .
The ServerAlias problem.
The ServerName is the primary hostname, but users often reach a site through additional hostnames, such as www.example.com in addition to example.com, or a legacy domain that redirects to the new one. The ServerAlias directive lists those additional hostnames. Without it, a request for www.example.com does not match a virtual host whose ServerName is example.com, and it falls through to the default host, which may serve the wrong site .
The port-binding problem.
A <VirtualHost> block is bound to an address and port: <VirtualHost *:80> listens on all addresses on port 80, and <VirtualHost 192.168.1.10:443> listens on a specific address on port 443. The Listen directive in the main configuration determines which ports Apache accepts connections on. A virtual host for a port that is not in the Listen list is never selected . On Debian, the ports.conf file contains the Listen directives, and the virtual host files are separate. On RHEL, the Listen directive is in httpd.conf, and the virtual host files are in conf.d/.
a. The name-based virtual host
The name-based virtual host is the most common form. The <VirtualHost> block contains the ServerName, the ServerAlias, the DocumentRoot, and a <Directory> block for the document root.
# /etc/apache2/sites-available/example.com.conf (Debian)
# /etc/httpd/conf.d/example.com.conf (RHEL)
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
<Directory /var/www/example.com>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
The ServerName is the primary hostname. The ServerAlias lists the additional hostnames that should be handled by this virtual host. The DocumentRoot sets the filesystem path, and the <Directory> block grants access to that path. The ErrorLog and CustomLog directives set per-site log files, which make it easier to diagnose issues with a specific site .
The ${APACHE_LOG_DIR} variable is defined in the Debian configuration and expands to /var/log/apache2. On RHEL, the equivalent path is /var/log/httpd, and the variable is not defined, so the path must be written explicitly.
# RHEL version with explicit paths
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
<Directory /var/www/example.com>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog /var/log/httpd/example.com-error.log
CustomLog /var/log/httpd/example.com-access.log combined
</VirtualHost>
The content of the block is the same; only the log paths differ. The combined format is the standard log format that includes the client IP, the request, the status code, and the user agent.
b. Enabling virtual hosts on Debian and RHEL
The mechanism for enabling a virtual host differs between the two families. Debian uses the a2ensite and a2dissite helper scripts to manage symlinks between sites-available and sites-enabled. RHEL places the configuration file in conf.d/, and it is included automatically by the main configuration.
# Debian: create the file in sites-available
sudo tee /etc/apache2/sites-available/example.com.conf > /dev/null <<'EOF'
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
<Directory /var/www/example.com>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
EOF
# Enable the site
sudo a2ensite example.com.conf
# Disable the default site if it conflicts
sudo a2dissite 000-default.conf
# RHEL: create the file in conf.d
sudo tee /etc/httpd/conf.d/example.com.conf > /dev/null <<'EOF'
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
<Directory /var/www/example.com>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog /var/log/httpd/example.com-error.log
CustomLog /var/log/httpd/example.com-access.log combined
</VirtualHost>
EOF
The a2ensite command creates a symlink in sites-enabled that points to the file in sites-available. The a2dissite command removes the symlink. The a2ensite and a2dissite helpers are part of the apache2 package on Debian and are not available on RHEL . On RHEL, the file in conf.d/ is included automatically by the IncludeOptional conf.d/*.conf directive in httpd.conf.
The default site on Debian is 000-default.conf, which listens on port 80 and serves /var/www/html. It should be disabled or modified when a new virtual host is added, because it is the first virtual host for port 80 and therefore the default host. If it is not disabled, requests that do not match any other virtual host are handled by the default site rather than the new site .
c. The default host and testing
The first <VirtualHost> block for a given address and port is the default host. If no other block matches the request’s Host header, the default host handles the request. The order of the blocks in the configuration files determines which one is first, and the a2ensite command creates symlinks that are included in alphabetical order.
# List the enabled sites in order
ls -l /etc/apache2/sites-enabled/
# Output: 000-default.conf -> ../sites-available/000-default.conf
# example.com.conf -> ../sites-available/example.com.conf
# The 000-default.conf is included first, so it is the default host
To make the new site the default, either disable the default site or name the new site’s symlink so that it sorts first. A common convention is to prefix the site name with a number, such as 001-example.com.conf, to control the order.
Testing uses curl with the Host header to simulate a request for a specific virtual host. The -H flag sets the header, and the URL can be http://localhost because the Host header overrides the default .
# Test the example.com virtual host
curl -H "Host: example.com" http://localhost
# Output: the content from /var/www/example.com/index.html
# Test the www alias
curl -H "Host: www.example.com" http://localhost
# Test an unknown hostname; it goes to the default host
curl -I -H "Host: unknown.com" http://localhost
The apache2ctl -S command (or httpd -S on RHEL) shows the virtual host configuration that Apache has parsed, including the default host and the ServerName and ServerAlias for each block. It is the definitive verification that the virtual hosts are recognized .
# Show the virtual host configuration
sudo apache2ctl -S # Debian
sudo httpd -S # RHEL
# Output lists each virtual host, its port, its name, and its aliases
Complete Example Session
# ============================================
# PART 1: CREATE THE SITE DIRECTORY
# ============================================
sudo mkdir -p /var/www/example.com
echo "<h1>Example Site</h1>" | sudo tee /var/www/example.com/index.html
sudo chown -R www-data:www-data /var/www/example.com # Debian
# sudo chown -R apache:apache /var/www/example.com # RHEL
# ============================================
# PART 2: CREATE THE VIRTUAL HOST FILE
# ============================================
sudo tee /etc/apache2/sites-available/example.com.conf > /dev/null <<'EOF'
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
<Directory /var/www/example.com>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
EOF
# ============================================
# PART 3: ENABLE THE SITE
# ============================================
sudo a2ensite example.com.conf
# ============================================
# PART 4: DISABLE THE DEFAULT SITE
# ============================================
sudo a2dissite 000-default.conf
# ============================================
# PART 5: VALIDATE THE CONFIGURATION
# ============================================
sudo apache2ctl configtest
# Output: Syntax OK
# ============================================
# PART 6: RELOAD APACHE
# ============================================
sudo systemctl reload apache2
# ============================================
# PART 7: TEST THE MAIN HOST
# ============================================
curl -H "Host: example.com" http://localhost
# Output: <h1>Example Site</h1>
# ============================================
# PART 8: TEST THE ALIAS
# ============================================
curl -H "Host: www.example.com" http://localhost
# Output: <h1>Example Site</h1>
# ============================================
# PART 9: TEST AN UNKNOWN HOST
# ============================================
curl -I -H "Host: unknown.com" http://localhost
# Output: the default host handles the request
# ============================================
# PART 10: VERIFY THE VIRTUAL HOST CONFIGURATION
# ============================================
sudo apache2ctl -S
# Output lists example.com on port 80 with the www alias
The ten parts demonstrate the complete virtual host workflow: content creation, configuration, enablement, disabling the default site, validation, reload, testing the main host, testing the alias, testing an unknown host, and verifying the parsed configuration. The critical verification is Part 10: apache2ctl -S shows exactly which virtual hosts Apache has recognized and which one is the default.
Quick Reference
Virtual Host Directives
| Directive | Purpose | Example |
|---|---|---|
<VirtualHost> | Container for a virtual host | <VirtualHost *:80> |
ServerName | Primary hostname | ServerName example.com |
ServerAlias | Additional hostnames | ServerAlias www.example.com |
DocumentRoot | Filesystem path | DocumentRoot /var/www/example.com |
<Directory> | Access control | <Directory /var/www/example.com> |
ErrorLog | Per-site error log | ErrorLog .../error.log |
CustomLog | Per-site access log | CustomLog .../access.log combined |
Enablement Commands
| Distribution | Enable | Disable | File Location |
|---|---|---|---|
| Debian / Ubuntu | a2ensite <name> | a2dissite <name> | /etc/apache2/sites-available/ |
| RHEL / Rocky | Automatic | Move file | /etc/httpd/conf.d/ |
Validation and Verification
| Command | Distribution | Purpose |
|---|---|---|
apache2ctl configtest | Debian | Validate syntax |
httpd -t | RHEL | Validate syntax |
apache2ctl -S | Debian | Show virtual host config |
httpd -S | RHEL | Show virtual host config |
curl -H "Host: ..." | Both | Test a specific virtual host |
Best Practices
โ Do This:
# Use ServerAlias for the www subdomain
ServerName example.com
ServerAlias www.example.com # โ
# Give each virtual host its own log files
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log # โ
# Disable the default site when adding a new one
a2dissite 000-default.conf # โ
# Validate before reloading
apache2ctl configtest # โ
# Verify with apache2ctl -S
apache2ctl -S # โ
# Test with the Host header
curl -H "Host: example.com" http://localhost # โ
โ Don’t Do This:
# Omit ServerAlias for the www subdomain
ServerName example.com # www.example.com falls to default # โ
# Leave the default site enabled
# It becomes the default host and handles unmatched requests # โ
# Omit the <Directory> block
DocumentRoot /var/www/example.com # 403 Forbidden # โ
# Use a wildcard <VirtualHost *:80> without ServerName
# Every request matches the first block # โ
# Reload without validation
systemctl reload apache2 # Syntax error prevents reload # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Wrong site served | Default host handles the request | Disable the default site; check order |
www subdomain fails | ServerAlias not set | Add ServerAlias www.example.com |
| 403 Forbidden on the new site | <Directory> block missing or permissions wrong | Add the block; chown to the web user |
| Virtual host not recognized | File not enabled or not in conf.d | a2ensite on Debian; move file on RHEL |
| Configuration error | Missing directive or typo | apache2ctl configtest reports the line |
| Logs mixed between sites | Shared ErrorLog and CustomLog | Set per-site log files |
Real-World Examples
1. Name-based virtual host
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
</VirtualHost>
2. DocumentRoot with access control
<Directory /var/www/example.com>
Options -Indexes +FollowSymLinks
Require all granted
</Directory>
3. Per-site logs
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
4. Enable the site on Debian
a2ensite example.com.conf
5. Disable the default site
a2dissite 000-default.conf
6. RHEL file placement
cp example.com.conf /etc/httpd/conf.d/
7. Validate configuration
apache2ctl configtest # Debian
httpd -t # RHEL
8. Show virtual host config
apache2ctl -S # Debian
httpd -S # RHEL
9. Test the main host
curl -H "Host: example.com" http://localhost
10. Test the alias
curl -H "Host: www.example.com" http://localhost
Visual
Virtual Host Selection
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ APACHE SELECTS THE VIRTUAL HOST BY Host HEADER โ
โ โ
โ Request: GET / HTTP/1.1 โ
โ Host: example.com โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ <VirtualHost *:80> โ โ
โ โ ServerName example.com โ MATCH โ โ
โ โ ServerAlias www.example.com โ โ
โ โ </VirtualHost> โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Request: GET / HTTP/1.1 โ
โ Host: unknown.com โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ No ServerName or ServerAlias match โ โ
โ โ โ First <VirtualHost> for the address and port โ โ
โ โ โ The default host โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Debian vs RHEL File Layout
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DEBIAN vs RHEL โ
โ โ
โ Debian / Ubuntu: โ
โ /etc/apache2/ โ
โ โโโ sites-available/ โ All site configs โ
โ โ โโโ 000-default.conf โ
โ โ โโโ example.com.conf โ
โ โโโ sites-enabled/ โ Symlinks to active sites โ
โ โโโ 000-default.conf โ ../sites-available/... โ
โ โโโ example.com.conf โ ../sites-available/... โ
โ โ
โ a2ensite creates the symlink. a2dissite removes it. โ
โ โ
โ RHEL / Rocky: โ
โ /etc/httpd/ โ
โ โโโ conf.d/ โ All .conf files auto-included โ
โ โโโ example.com.conf โ
โ โโโ other.com.conf โ
โ โ
โ Files are enabled by their presence in conf.d. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Default Host Selection
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE FIRST VIRTUAL HOST IS THE DEFAULT โ
โ โ
โ sites-enabled/ (in alphabetical order): โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ 000-default.conf โ first โ DEFAULT HOST โ โ
โ โ example.com.conf โ second โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ If 000-default.conf is enabled: โ
โ โ It handles requests that match no other host โ
โ โ The new site is only served for its own hostnames โ
โ โ
โ If 000-default.conf is disabled: โ
โ โ example.com.conf becomes the default host โ
โ โ It handles unmatched requests as well โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Virtual host container | <VirtualHost *:80> |
| Primary hostname | ServerName |
| Additional hostnames | ServerAlias |
| Filesystem path | DocumentRoot |
| Access control | <Directory> + Require |
| Per-site logs | ErrorLog, CustomLog |
| Debian enablement | a2ensite <name> |
| Debian disablement | a2dissite <name> |
| RHEL enablement | File in conf.d/ |
| Validation (Debian) | apache2ctl configtest |
| Validation (RHEL) | httpd -t |
| Config verification | apache2ctl -S / httpd -S |
| Default host | First virtual host for the address and port |
Key takeaways:
- Apache selects the virtual host by the
Hostheader. TheServerNameandServerAliasdirectives define the hostnames that the block responds to. A request whose hostname matches neither falls to the default host . ServerAliasis required for additional hostnames. TheServerNameis the primary hostname, and theServerAliaslists the alternatives. Without it,www.example.comdoes not match a virtual host whoseServerNameisexample.com.- The first virtual host is the default. If no block matches, Apache uses the first block for the address and port. The order is determined by the alphabetical order of the files in
sites-enabledon Debian, and by the order of the blocks on RHEL. - Debian and RHEL use different enablement mechanisms. Debian uses
a2ensiteanda2dissiteto manage symlinks. RHEL places the file inconf.d/, where it is included automatically . - Each site should have its own logs. The
ErrorLogandCustomLogdirectives inside the<VirtualHost>block write to per-site files, which makes it possible to diagnose issues with a specific site without scanning a combined log. apache2ctl -Sshows the parsed configuration. It lists every virtual host, its port, its name, and its aliases, and it identifies the default host. It is the definitive verification that the virtual hosts are recognized .
Remember: A virtual host is a <VirtualHost> container with a ServerName that distinguishes it from the others. The Host header selects the block, and the first block is the default when no name matches. The ServerAlias covers the additional hostnames, and the <Directory> block grants access to the document root. Debian enables sites with a2ensite; RHEL enables them by placing the file in conf.d/. Validate with configtest or -t, verify with -S, and test with curl and the correct Host header. A configuration that parses is not necessarily a configuration that serves the right site; the verification confirms both.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!