← News
/PLAYBOOK · 2026-09-08

Hiding the Nginx Version: Stop Fingerprint Leakage

Default Nginx exposes its version in response headers and error pages; attackers match CVEs from that. server_tokens off, X-Powered-By stripping, headers-more Server rewrite, custom error pages, curl verification, module incompatibility.

Applicable scenarios

Prerequisites

How it works

Nginx defaults to server_tokens on. Two leak points: response header Server: nginx/1.24.0 with the exact version; default 404/50x error-page footer nginx/1.24.0. Some distro builds also stamp an OS identifier. Exact version lets attackers filter unpatched CVEs and run PoCs cheaply. Version hiding shrinks attack surface: it does not patch vulns, it denies cheap targeting.

Reverse proxy / FastCGI also leaks app versions via X-Powered-By: PHP/8.2.1. Strip that too.

Procedure

1. Disable server_tokens

`http {
    server_tokens off;
}`

After this, Server is just nginx; error-page footer is nginx without a version.

2. Hide the PHP version header

`http {
    fastcgi_hide_header X-Powered-By;
}`

3. Fully rewrite the Server header (optional)

If the baseline forbids the word nginx in Server, install headers-more:

`# CentOS/RHEL
yum install -y nginx-module-headers-more
# Ubuntu/Debian
apt install -y libnginx-mod-http-headers-more-filter`

Load it in the http block and rewrite:

`load_module modules/ngx_http_headers_more_filter_module.so;
http {
    more_set_headers "Server: WebServer";
}`

Dynamic modules must match the running Nginx version exactly. Prefer distro module packages. Hand-compiling often fails nginx -t with module incompatibility.

4. Custom error pages

`server {
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /404.html { root /usr/share/nginx/html; internal; }
    location = /50x.html { root /usr/share/nginx/html; internal; }
}`

server_tokens off already strips versions from default error pages. Custom pages further avoid leaking internal paths and module info.

5. Reload

`nginx -t && nginx -s reload`

Verification

`curl -sI https://helixcyber.ai | grep -i server
# Expected: Server: nginx (no version number)
# After headers-more: Server: WebServer`

Hit a missing path to trigger 404; confirm the footer has no nginx/1.24.0. Check curl -sI https://helixcyber.ai | grep -i powered — X-Powered-By should be gone.

FAQ

FAQ 1: server_tokens off, 502 page still shows the version?

Typical reverse-proxy case: the 502 page is generated upstream and passed through. http-block server_tokens only affects responses Nginx itself generates. Add proxy_intercept_errors on; in the relevant server/location and define error_page so Nginx owns the error page, or more_clear_headers on the upstream response.

FAQ 2: After headers-more, nginx -t says module is not binary compatible?

Module was built against a different Nginx version or configure flags. Record current flags with nginx -V, rebuild with the same ./configure --add-dynamic-module=..., or install the matching distro module package. Do not copy an .so built for version A onto version B.

FAQ 3: Is hiding the version enough?

No. It only raises recon cost. Vulns still need patches. Treat version hiding as a baseline item and regularly upgrade Nginx to the latest stable.

Summary

Nginx version hiding is the cheapest, most certain baseline: one server_tokens off line strips header and error-page versions; fastcgi_hide_header clears the PHP fingerprint; headers-more rewrites Server if required. Recheck both headers and error pages with curl. On reverse proxies, watch upstream error-page passthrough.