# Fix Apache VirtualHost Not Matching Correct ServerName

You have multiple VirtualHost blocks configured in Apache, but requests to api.example.com are being served by the www.example.com VirtualHost. The wrong SSL certificate is returned, or the wrong document root is used.

How Apache Selects a VirtualHost

Apache selects VirtualHosts using this order:

  1. 1.IP address and port: Apache first matches the incoming request's destination IP and port against VirtualHost declarations
  2. 2.ServerName and ServerAlias: Within matching IP:port blocks, Apache matches the Host header against ServerName and ServerAlias
  3. 3.First VirtualHost as default: If no ServerName matches, Apache uses the first VirtualHost defined for that IP:port

Diagnosing the Mismatch

List all configured VirtualHosts:

bash
apache2ctl -S
# or
httpd -S

This shows the VirtualHost configuration as Apache understands it:

bash
VirtualHost configuration:
*:443                  is a NameVirtualHost
         default server www.example.com (/etc/apache2/sites-enabled/000-default.conf:1)
         port 443 namevhost www.example.com (/etc/apache2/sites-enabled/000-default.conf:1)
         port 443 namevhost api.example.com (/etc/apache2/sites-enabled/api.conf:1)
         port 443 namevhost blog.example.com (/etc/apache2/sites-enabled/blog.conf:1)
*:80                   is a NameVirtualHost
         default server www.example.com (/etc/apache2/sites-enabled/000-default.conf:10)
         port 80 namevhost www.example.com (/etc/apache2/sites-enabled/000-default.conf:10)
  1. 1.If a request to api.example.com is being served by www.example.com, check:
  2. 2.Is api.example.com listed in the output for the correct port?
  3. 3.Is the ServerName directive spelled correctly?

Common Causes

1. Wrong Port in VirtualHost

```apache # WRONG: api.example.com is on port 80, but request comes on port 443 <VirtualHost *:80> ServerName api.example.com DocumentRoot /var/www/api </VirtualHost>

# CORRECT: Match the incoming port <VirtualHost *:443> ServerName api.example.com DocumentRoot /var/www/api # SSL configuration </VirtualHost> ```

2. Missing ServerName

apache
<VirtualHost *:443>
    # No ServerName -- this becomes the default for all unmatched requests on 443
    DocumentRoot /var/www/default
</VirtualHost>

Always include a ServerName directive. The VirtualHost without a ServerName becomes the catch-all default.

3. ServerAlias Typo

apache
<VirtualHost *:443>
    ServerName api.example.com
    ServerAlias api.example.con  # Typo: .con instead of .com
    DocumentRoot /var/www/api
</VirtualHost>

4. Configuration File Loading Order

Apache loads configuration files alphabetically within each configuration directory. The first VirtualHost for a given IP:port becomes the default:

bash
ls -la /etc/apache2/sites-enabled/
# 000-default.conf loads before api.conf
# 000-default becomes the default server

This means requests with unrecognized or missing Host headers go to 000-default. Ensure your default VirtualHost is intentional.

Testing VirtualHost Matching

```bash # Test which VirtualHost handles a specific host curl -sI -H "Host: api.example.com" http://127.0.0.1/ | grep -E "HTTP/|Server:"

# Use apache2ctl to check matching apache2ctl -S 2>&1 | grep "api.example.com" ```

SNI (Server Name Indication) for SSL

For HTTPS VirtualHosts, the client must support SNI to send the hostname before the TLS handshake. Test SNI:

bash
echo | openssl s_client -connect example.com:443 -servername api.example.com 2>&1 | grep "subject="

If the certificate returned does not match api.example.com, SNI is not working correctly. Ensure:

apache
<VirtualHost *:443>
    ServerName api.example.com
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/api.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/api.example.com/privkey.pem
</VirtualHost>

Each VirtualHost must have its own SSL certificate. Apache uses the ServerName from the TLS ClientHello SNI extension to select the correct certificate.

The Catch-All VirtualHost

Create an intentional catch-all for unknown hosts:

```apache <VirtualHost *:80> ServerName _ DocumentRoot /var/www/catchall

# Return 444 for unknown hosts (Nginx-style, Apache equivalent) <Location "/"> RewriteEngine On RewriteRule ^ - [R=403,L] </Location> </VirtualHost> ```

Place this as the first VirtualHost file (e.g., 000-catchall.conf) so it becomes the default. This prevents accidental serving of content from other VirtualHosts for requests with missing or unrecognized Host headers.