Websites & apps

Hide the Nginx version and harden the default config

Make Nginx give away less and accept less. Hide the version number, drop requests for unknown hosts, block dotfiles, limit methods and body size, and tighten timeouts.

BeginnerLinux server5 min read

A fresh Nginx install is safe, but chatty. It announces its exact version in headers and error pages, answers any hostname pointed at your IP, and will happily serve a stray .git folder if one ends up in a web root. A few small changes fix all of that. This guide applies a short hardening checklist you can roll out to every site on the server.

Before you start

  • A Linux VPS with Nginx installed and at least one working site.
  • SSH access with sudo.
  • A backup of the config: sudo cp -a /etc/nginx /etc/nginx.bak-$(date +%F).

Hide the version number

By default the Server header and error pages show something like nginx/1.24.0. That tells scanners exactly which known bugs to try. Turn it off in the http context. On Ubuntu and Debian, nginx.conf usually contains a commented line you can enable:

Bash
grep -n "server_tokens" /etc/nginx/nginx.conf

If it shows # server_tokens off;, remove the #. If there is no line at all, add it to a new file instead:

Bash
echo "server_tokens off;" | sudo tee /etc/nginx/conf.d/hardening.conf

Only set it once, or nginx -t reports a duplicate directive. The header becomes plain Server: nginx. Removing the header entirely needs a third-party module and gains little.

Note: Hiding the version is a small step, not protection on its own. Keeping Nginx updated matters far more. See Automatic security updates on Linux.

Drop requests for unknown hosts

Bots scan IP ranges and send requests with your bare IP or random hostnames. Without a catch-all, Nginx serves them the first site it finds. Add a default server that answers nothing:

Bash
sudo nano /etc/nginx/conf.d/00-default.conf
Nginx
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;
    ssl_reject_handshake on;
}
  • return 444 closes the connection with no response.
  • ssl_reject_handshake on refuses the TLS handshake for unknown names, so no certificate is needed in this block. It needs Nginx 1.19.4 or newer, which all the distributions covered here ship.

On Ubuntu and Debian, the packaged default site also claims default_server on port 80. Remove its link first, or nginx -t fails with "a duplicate default server":

Bash
sudo rm /etc/nginx/sites-enabled/default

On AlmaLinux and Rocky, the main nginx.conf may contain a server block with default_server. Comment out that whole block or remove the default_server flag from its listen lines.

Block hidden files

Files starting with a dot, such as .git, .env and .htpasswd, often contain secrets. Block them in every site's server block while still allowing .well-known, which Let's Encrypt and other services use:

Nginx
location ~ /\.(?!well-known) {
    deny all;
    access_log off;
    log_not_found off;
}

Put it near the top of the server block so it matches before other regex locations.

Limit request methods

Most websites only need GET, HEAD and POST. Reject the rest inside the server block:

Nginx
if ($request_method !~ ^(GET|HEAD|POST)$) {
    return 405;
}

Warning: REST APIs, WebDAV and some apps use PUT, PATCH, DELETE or OPTIONS (for CORS preflight). Do not add this to those sites, or add the methods they need to the list.

Limit body size and timeouts

Add these to /etc/nginx/conf.d/hardening.conf:

Nginx
client_max_body_size 10m;
client_body_timeout 15s;
client_header_timeout 15s;
send_timeout 30s;
keepalive_timeout 30s;
  • client_max_body_size caps uploads. Raise it for sites that need bigger uploads in their own server block.
  • The timeouts stop slow clients from holding connections open forever.

Check none of these are already set in nginx.conf. On Ubuntu and Debian, keepalive_timeout usually is; edit it there rather than adding a second copy.

Disable directory listings

autoindex is off by default. Make sure no site switched it on by accident:

Bash
sudo grep -rn "autoindex on" /etc/nginx/

Apply the change

Bash
sudo nginx -t && sudo systemctl reload nginx

If the test fails, it names the file and line. Fix it, or restore your backup with sudo cp -a /etc/nginx.bak-YYYY-MM-DD/. /etc/nginx/.

Check it works

Version hidden:

Bash
curl -sI https://example.com/ | grep -i "^server"

You should see server: nginx with no number.

Unknown hosts dropped:

Bash
curl -I http://203.0.113.10/

curl should report Empty reply from server.

Dotfiles blocked:

Bash
curl -I https://example.com/.env

You want HTTP/2 403, not a 200 or a download.

Your real sites should still load normally in a browser.

Troubleshooting

  • nginx -t says "duplicate default server": another file also uses default_server on the same port. Remove the flag from one of them.
  • nginx -t says "server_tokens" directive is duplicate: it is set in nginx.conf and in your new file. Keep one.
  • Your site now shows an empty reply: its server_name does not match the domain, so the catch-all answers. Fix the server_name.
  • Let's Encrypt renewals fail: your dotfile rule blocks .well-known. Use the pattern with (?!well-known) exactly as shown.
  • Uploads fail with 413: client_max_body_size is too low for that site. See Fix Nginx 413 Request Entity Too Large.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.