Skip to content

maxuru ~ % cat nginx/server-tokens

Hide the nginx version with server_tokens

Severity: lowApplies to: nginx 1.25+Applies to: nginx 1.30 / 1.31Applies to: freenginx
The fix/etc/nginx/nginx.conf
http {
server_tokens off;
}
Terminal window
sudo nginx -t && sudo systemctl reload nginx

Note the semicolon. server_tokens off without one is a config that will not load, and it is the most common way this single line fails.

Open-source nginx accepts exactly three, and no others:

Value Server header Notes
on Server: nginx/1.30.4 The default
off Server: nginx What you want
build Server: nginx/1.30.4 (build-name) Reveals more, never less

build is the one worth understanding, because its name suggests a middle ground and it is the opposite. Internally the build string is defined as the full version plus the build name, so build is on with something extra appended — and if the packager never set a build name, it is byte-for-byte identical to on. There is no configuration in which build discloses less than on does.

Values are matched case-insensitively, so Off and OFF work. Anything outside the set — true, false, 0, 1, none, hide — is a fatal config error, not a silently ignored line.

server_tokens is valid in three contexts: http, server and location. It takes exactly one argument.

http {
server_tokens off; # applies to every server and location below
server {
listen 443 ssl;
server_tokens off; # valid, but redundant if http already says off
}
}

Put it in the http block. That single placement covers every virtual host and every location, which is the behaviour you want and the reason the fix above shows it there.

It is not valid at the top level of nginx.conf, nor inside events, nor inside an if block — all three produce a fatal directive is not allowed here. Placing it at the top of the file, above http, is the usual mistake, because that is where user, worker_processes and pid live and it looks like a global setting.

Unlike add_header, this directive inherits normally: a nested block that does not mention server_tokens keeps whatever the enclosing block set. The trap is the reverse one — an explicit server_tokens on; in a single server block silently overrides the global off for that host only.

server_tokens off changes this:

Server: nginx/1.30.4

into this:

Server: nginx

That is the entire effect. It does not remove the Server header, and it does not hide that you’re running nginx. It removes four digits.

This is obscurity, and it is worth doing for one narrow reason: mass scanners match on version strings. A bot working through the internet looking for hosts vulnerable to a specific CVE will read your banner and move on if it doesn’t match. Removing the version drops you out of that particular bucket.

It buys you nothing at all against anyone who has decided to look at you. Response fingerprinting identifies nginx and narrows the version range without the banner, and error page formatting gives it away too. Do not count this as a control — count it as reducing untargeted noise, which is the same value Fail2Ban offers on SSH, and for the same reason it is rated low.

The version number is not the vulnerability. If you’re on 1.30.4 you’re patched regardless of who knows it; if you’re on an unpatched version, hiding the banner does not unpatch the attacker. Patching is the control. This is housekeeping.

Every server has a version of this directive and the reasoning transfers intact — Apache’s equivalent is ServerTokens Prod, and that page reaches the same conclusion for the same reason: the real disclosure on a web server is directory listings, not the banner.

Removing the Server header entirely needs a module

Section titled “Removing the Server header entirely needs a module”

Stock nginx cannot delete the Server header — server_tokens is the only lever, and off is as far as it goes. To remove or replace it you need headers-more-nginx-module:

more_clear_headers 'Server';

That module is packaged as nginx-extras on Debian and Ubuntu, and is the same module worth having for security headers, where it solves a genuinely painful problem. Installing a third-party module purely to hide a banner is not a good trade; installing it for header inheritance and getting this as a bonus is.

server_tokens off also removes the version from nginx’s built-in error page footers, which otherwise print nginx/1.30.4 in the HTML of every 404 and 502. That’s usually where people find it after “removing” it from the header — the directive handles both, but only if it’s in scope.

Put it in the http block so it applies everywhere. A server_tokens on in one server block overrides it for that host.