How to Configure Nginx Default Server Block to Catch All Unhandled Virtual Host Requests


2 views

When running multiple websites on a single Nginx server, you'll typically configure multiple server blocks (virtual hosts). But what happens when a request comes in that doesn't match any of your defined server_name directives? Maybe it's:

  • A direct IP address access
  • A mistyped domain name
  • An old domain that used to point to your server
  • A malicious scan probing your server

By default, Nginx will use the first server block it finds as the default catch-all. This is often not what you want, especially if your first vhost is for a production website.

The proper way is to define an explicit default server block that will catch all unhandled requests:


server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    
    # For HTTPS
    # listen 443 ssl default_server;
    # listen [::]:443 ssl default_server;
    # ssl_certificate /path/to/default/cert;
    # ssl_certificate_key /path/to/default/key;
    
    return 444; # Close connection silently
    # Alternative options:
    # return 403; # Forbid access
    # rewrite ^ https://yourmaindomain.com$request_uri permanent; # Redirect to primary domain
}

Here are some common approaches for the default server block:

Option 1: Silent Connection Closing (444)

The Nginx-specific 444 code immediately closes the connection:


return 444;

Option 2: Redirect to Primary Domain


return 301 https://yourmaindomain.com$request_uri;

Option 3: Custom Error Page


root /var/www/default;
index index.html;
location / {
    try_files $uri $uri/ =404;
}

Here's how this looks in a real configuration with multiple vhosts:


# Default catch-all (must come first)
server {
    listen 80 default_server;
    server_name _;
    return 444;
}

# Production website
server {
    listen 80;
    server_name example.com www.example.com;
    # ... rest of config ...
}

# Development site
server {
    listen 80;
    server_name dev.example.com;
    # ... rest of config ...
}

After implementing, test with:


curl -v http://your.server.ip
curl -v http://nonexistent-domain.test

When dealing with multiple virtual hosts in Nginx, you might encounter situations where requests come in that don't match any configured server_name. These could be:

  • Direct IP access to your server
  • Domains pointing to your server but not configured
  • Typos in domain names
  • Legacy domains no longer in use

Nginx processes server blocks in the order they're loaded, and the first server block becomes the default for unmatched requests. Here's the proper way to set this up:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    
    # For HTTPS (if applicable)
    # listen 443 ssl default_server;
    # listen [::]:443 ssl default_server;
    
    # Basic logging for debugging
    access_log /var/log/nginx/default_access.log;
    error_log /var/log/nginx/default_error.log;
    
    # You can choose to:
    # 1. Return 444 (Nginx-specific "close connection")
    # 2. Redirect to your main domain
    # 3. Show a custom maintenance page
    
    # Option 1: Close connection silently
    return 444;
    
    # Option 2: Redirect to primary domain
    # return 301 https://yourmaindomain.com$request_uri;
    
    # Option 3: Serve a custom page
    # root /var/www/default;
    # index index.html;
    # try_files $uri /index.html =404;
}

Key elements to remember when setting up your default server block:

  • The default_server parameter must be specified in the listen directive
  • server_name _; is a catch-all pattern
  • Place this configuration before other server blocks in your config files
  • For HTTPS, you'll need a wildcard or default SSL certificate

For more complex setups, you might want to handle different scenarios:

# /etc/nginx/conf.d/default.conf
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    
    # Detect if request came via HTTPS but hit this HTTP block
    if ($http_x_forwarded_proto = "https") {
        return 301 https://$host$request_uri;
    }
    
    # Handle known bots differently
    if ($http_user_agent ~* (bot|crawl|spider)) {
        root /var/www/bot_trap;
        try_files $uri /bot_message.html =404;
    }
    
    # All other cases
    return 301 https://yourmaindomain.com$request_uri;
}

After implementing, test with these commands:

sudo nginx -t
sudo systemctl reload nginx

Then verify using:

curl -I http://your.server.ip
curl -H "Host: randomdomain.com" http://your.server.ip