How to Block Invalid Host Headers in Nginx with 444 Response


3 views

When running a production web server, it's common to receive requests with malformed or malicious Host headers. These can come from:

  • Misconfigured clients or proxies
  • Security scanners probing for vulnerabilities
  • Old DNS records pointing to your IP
  • Attempts at virtual host brute-forcing

A common pitfall occurs when Nginx uses the first server block as the default catch-all. Here's why this is problematic:

# This becomes the default handler if no Host matches
server {
    listen 80 default_server;
    # ... this might serve content unexpectedly
}

Here's how to properly implement Host header validation:

server {
    listen 80 default_server;
    server_name _;
    return 444;  # Close connection without response
}

server {
    server_name www.olddomain.com olddomain.com www.newdomain.com;
    listen 80;
    return 301 $scheme://newdomain.com$request_uri;
}

server {
    server_name newdomain.com localhost;
    listen 80;
    # Main configuration
}

For enhanced security, consider these additions:

# Block requests with empty Host headers
if ($host = "") {
    return 444;
}

# Validate Host against regex pattern
if ($host !~* ^(www\.)?(olddomain|newdomain)\.com$) {
    return 444;
}

The 444 response is particularly efficient because:

  • No HTTP response is generated
  • TCP connection is immediately closed
  • Minimal CPU and memory usage

This makes it ideal for handling large volumes of malicious traffic.

To monitor blocked requests:

log_format blocked '$remote_addr - $http_host';
server {
    listen 80 default_server;
    server_name _;
    access_log /var/log/nginx/blocked.log blocked;
    return 444;
}

When running a web server, you'll often encounter requests with incorrect or malicious Host headers. These can come from:

  • Old DNS records pointing to your IP
  • Direct IP access attempts
  • Malicious bots scanning servers
  • Misconfigured applications

By default, Nginx will process any request reaching the server's IP address, regardless of the Host header. This can lead to:

# Default catch-all behavior
server {
    listen 80 default_server;
    # This will handle ANY request to the IP
}

Here's how to implement proper Host header validation:

server {
    listen 80 default_server;
    server_name _;
    return 444; # Close connection without response
}

server {
    server_name www.olddomain.com olddomain.com www.newdomain.com;
    listen 80;
    return 301 $scheme://newdomain.com$request_uri;
}

server {
    server_name newdomain.com localhost;
    listen 80;
    
    # Your actual configuration
    root /var/www/html;
    index index.html;
    # ... other directives
}

For more complex scenarios, you might want to:

# Log invalid host attempts
server {
    listen 80 default_server;
    server_name _;
    
    access_log /var/log/nginx/invalid_host.log;
    return 444;
}

# Handle specific invalid domains differently
map $host $invalid_host {
    default 0;
    ~^(www\.)?badguy\.com$ 1;
}

server {
    listen 80;
    server_name ~^(www\.)?(old|new)domain\.com$ localhost;
    
    if ($invalid_host) {
        return 403 "Forbidden";
    }
    
    # Normal processing
}

Verify with curl:

# Valid request
curl -H "Host: newdomain.com" http://your.server.ip

# Invalid request (should get dropped)
curl -H "Host: fake.com" http://your.server.ip

# Check logs
tail -f /var/log/nginx/invalid_host.log

Nginx's 444 is special because:

  • It immediately closes the connection
  • No response is sent to the client
  • Conserves bandwidth
  • Prevents information leakage