How to Configure Nginx to Properly Handle Unknown Hostnames and Return 404


4 views

When running multiple domains on a single Nginx instance, you might encounter an unexpected behavior: Nginx serves content from your primary domain even when requests hit undefined hostnames. This occurs because Nginx automatically uses the first server block as the default when no matching server_name is found.

To properly handle undefined domains, we need to create a catch-all server block that explicitly returns 404. Here's the correct implementation:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 404;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;
    ssl_certificate /path/to/dummy.crt;
    ssl_certificate_key /path/to/dummy.key;
    return 404;
}

server {
    listen 80;
    server_name web.example.com;
    # Your web configuration
}

server {
    listen 443 ssl;
    server_name web.example.com;
    ssl_certificate /path/to/web.crt;
    ssl_certificate_key /path/to/web.key;
    # Your web configuration
}
  • The default_server parameter forces Nginx to use this block for unmatched requests
  • server_name _ is a special wildcard that matches any hostname
  • For HTTPS, you must provide dummy certificates (self-signed works)
  • Place default blocks before other server declarations

Verify your setup works by making requests to undefined domains:

curl -I http://random.example.com
curl -Ik https://random.example.com

Both should return HTTP 404 status codes.

Some administrators prefer redirecting unknown hosts instead of showing 404s:

server {
    listen 80 default_server;
    server_name _;
    return 301 https://web.example.com$request_uri;
}

For security and SEO:

  • Always configure a default server
  • Consider logging invalid host access attempts
  • Regularly review your SSL/TLS configuration
  • Monitor for potential DNS spoofing attempts

When running multiple domains on a single Nginx instance, you might encounter this scenario:

# Current problematic config:
server {
    listen 80;
    server_name web.example.com;
    # web configuration
}

server {
    listen 443;
    server_name web.example.com;
    # HTTPS configuration
}

Despite only defining web.example.com, requests to home.example.com (pointing to the same IP) still get served by the web configuration. This happens because Nginx defaults to using the first defined server block when no match is found.

Nginx evaluates server blocks in this order:

  1. Exact server_name match
  2. Wildcard match (e.g., *.example.com)
  3. Regular expression match
  4. Default server (first defined or explicitly marked)

To properly handle unknown hostnames, we need to:

# Correct configuration example
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;  # Catch-all invalid hostnames
    
    ssl_certificate /path/to/dummy.crt;
    ssl_certificate_key /path/to/dummy.key;
    
    return 404;  # Or 444 to close connection silently
}

server {
    listen 80;
    server_name web.example.com;
    # web HTTP configuration
}

server {
    listen 443 ssl;
    server_name web.example.com;
    # web HTTPS configuration
}

For HTTPS traffic, you'll need dummy certificates for the default server. Here's how to generate them:

openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
    -keyout /etc/nginx/ssl/dummy.key \
    -out /etc/nginx/ssl/dummy.crt \
    -subj "/CN=invalid"

If you prefer handling HTTP and HTTPS differently:

# HTTP-only default server
server {
    listen 80 default_server;
    server_name _;
    return 444;  # Close connection immediately
}

# HTTPS default server
server {
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate /path/to/dummy.crt;
    ssl_certificate_key /path/to/dummy.key;
    return 403;  # Or other appropriate status
}

After making changes, always verify:

nginx -t
systemctl reload nginx

Test with these commands:

curl -I http://unknown.example.com
curl -Ik https://unknown.example.com