How to Properly Configure Nginx Default Server for HTTPS with SSL Termination


2 views

When working with multiple virtual hosts on a single Nginx instance, configuring a proper default server for HTTPS behaves differently than HTTP. Many administrators discover this the hard way when their carefully crafted SSL configurations suddenly start intercepting all traffic.

The key distinction lies in how Nginx processes SSL connections. Unlike HTTP where server_name matching works as expected, HTTPS connections undergo SSL termination before server name inspection. This means the SSL certificate must be presented first, which creates complications for default server handling.

Here's the correct way to implement a default HTTPS server that won't interfere with other virtual hosts:

# Default catch-all for invalid HTTPS requests
server {
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate /path/to/default/cert.pem;
    ssl_certificate_key /path/to/default/key.pem;
    
    # Close connection immediately
    return 444;
}

# Specific SSL host example
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /path/to/example.com/cert.pem;
    ssl_certificate_key /path/to/example.com/key.pem;
    
    # Normal server configuration
    ...
}

Three essential components make this work properly:

  • A valid SSL certificate must be configured for the default server (can be self-signed)
  • The default server must appear before other server blocks in your configuration
  • Each virtual host needs its own SSL certificate configured

The original configuration likely failed because:

  1. Missing SSL certificate for the default server
  2. Incorrect processing order of server blocks
  3. Potential SNI (Server Name Indication) issues with older clients

For more complex setups with mixed HTTP/HTTPS hosts:

# HTTP default
server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

# HTTPS default (catch invalid requests)
server {
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate /etc/nginx/ssl/default.crt;
    ssl_certificate_key /etc/nginx/ssl/default.key;
    return 444;
}

# Valid HTTPS host
server {
    listen 443 ssl;
    server_name valid.example.com;
    ssl_certificate /etc/nginx/ssl/valid.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/valid.example.com.key;
    
    location / {
        # Normal configuration
    }
}

After implementing these changes:

sudo nginx -t
sudo systemctl reload nginx

Test with:

curl -kv https://invalid-domain.com
curl -kv https://valid.example.com

When configuring multiple virtual hosts with mixed HTTP/HTTPS setups, the behavior of Nginx's default server can sometimes be counterintuitive, especially with SSL/TLS connections. Many administrators find that their HTTPS default server behaves differently from their HTTP counterpart.

Nginx selects the default server based on:

  • The default_server parameter in listen directives
  • The first server block when no explicit default is specified
  • Port and protocol combination (80 vs 443)

A critical difference between HTTP and HTTPS default servers is SSL certificate handling. Unlike HTTP, Nginx must terminate SSL before processing the Host header. This creates a chicken-and-egg problem:

server {
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate /path/to/default/cert.pem;
    ssl_certificate_key /path/to/default/key.pem;
    return 444;
}

For a working HTTPS default server that doesn't hijack all connections:

# Default catch-all HTTPS server
server {
    listen 443 ssl default_server;
    server_name _;
    
    # Use a generic/wildcard certificate
    ssl_certificate /etc/ssl/wildcard.example.com.crt;
    ssl_certificate_key /etc/ssl/wildcard.example.com.key;
    
    # Immediately close connection
    return 444;
    
    # Alternative: serve maintenance page
    # root /var/www/maintenance;
    # location / { try_files $uri /maintenance.html; }
}

# Specific HTTPS hosts
server {
    listen 443 ssl;
    server_name app1.example.com;
    
    ssl_certificate /etc/ssl/app1.example.com.crt;
    ssl_certificate_key /etc/ssl/app1.example.com.key;
    
    # Normal configuration...
}

The issue occurs because:

  1. Nginx needs to complete SSL handshake before Host header inspection
  2. Without proper certificates, connections fail during handshake
  3. The default server's certificate is used for all unmatched requests

For more control over SSL default behavior:

# Reject non-SNI clients at the default server
server {
    listen 443 ssl default_server;
    server_name _;
    ssl_reject_handshake on;
}

# Alternative: Use a wildcard certificate
server {
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate /etc/ssl/wildcard.example.com.crt;
    ssl_certificate_key /etc/ssl/wildcard.example.com.key;
    
    # Verify Host header after SSL handshake
    if ($host !~* ^(valid1.com|valid2.com)$) {
        return 444;
    }
}

Verify your configuration with:

openssl s_client -connect example.com:443 -servername wrong.host

And check Nginx logs for SSL handshake errors and host matching.