maxuru ~ % cat nginx/server-tokens
Hide the nginx version with server_tokens
/etc/nginx/nginx.confhttp { server_tokens off;}sudo nginx -t && sudo systemctl reload nginxNote 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.
The three values
Section titled “The three values”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.
Where the directive is valid
Section titled “Where the directive is valid”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.
Be clear about what this buys you
Section titled “Be clear about what this buys you”server_tokens off changes this:
Server: nginx/1.30.4into this:
Server: nginxThat 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.
Don’t forget error pages
Section titled “Don’t forget error pages”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.
Related
Section titled “Related”- Add a default server block — the other reconnaissance surface, and a more useful fix.
- Set security headers correctly — where
headers-moreearns its place, and the directive that does not inherit normally. - Apache: disable information disclosure —
ServerTokens Prod, the same trade-off on the other web server. - Hardening nginx — the full checklist, in the order worth applying it.