Skip to content

Add a default server block in nginx

Severity: mediumApplies to: nginx 1.19.4+Applies to: nginx 1.30 / 1.31Applies to: freenginx
The fix/etc/nginx/conf.d/00-default.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
return 444;
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_reject_handshake on;
}
Terminal window
sudo nginx -t && sudo systemctl reload nginx

When a request arrives with a Host header that matches no server_name, nginx has to answer with something. Without an explicit default_server, it uses the first server block for that listen address — chosen by config load order, which is alphabetical filename order on Debian and Ubuntu.

So the site that answers for every scanner, every stale DNS record and every attacker probing by IP is whichever vhost happens to sort first. That’s an accident, and it has consequences:

  • Your certificate is presented to anyone, for any name. TLS handshake first, Host check later — so a scanner probing your IP gets your certificate, which names your domains. That’s free reconnaissance.
  • A stale DNS record keeps working. A domain that used to point at you, now owned by someone else, still resolves and still gets served your first vhost.
  • Host-header confusion. Applications that build URLs from Host can be induced to emit links pointing at an attacker’s domain — password reset emails being the classic.

An explicit default block makes the answer a deliberate one: nothing.

ssl_reject_handshake on (nginx 1.19.4+) rejects the TLS handshake without presenting a certificate at all. That is better than serving a self-signed cert or a dummy vhost, because it means an IP scan learns nothing — no names, no issuer, no expiry.

It needs no certificate configured, which also removes the usual chicken-and-egg of “what cert does the reject block use”.

return 444 on port 80 is nginx’s non-standard code meaning close the connection with no response. No body, no headers, no version banner — and no bandwidth spent on a scanner.

Two things people get wrong:

  • It’s a parameter of listen, not a standalone directive, and it applies per address:port. A default_server on 80 does nothing for 443. You need both, and both IPv4 and IPv6.
  • Only one server block per address:port may carry it. A second one is a config error and nginx refuses to start — which is exactly why you test with nginx -t before reloading, since a pre-existing default in the packaged sites-enabled/default will collide with yours.