Why a Checklist Approach Works
Hardening Nginx isn't a single setting - it's the cumulative effect of dozens of small defaults being deliberately overridden: disabling version disclosure, restricting TLS ciphers, running workers as an unprivileged user, limiting request sizes, and removing unused modules. A checklist forces you to verify each of these independently rather than assuming that a generally 'secure-looking' config has covered everything, since a single forgotten directive (like leaving server_tokens on) can undo the value of everything else.
Cricket analogy: It's like a fielding captain's pre-match checklist covering pitch report, weather, opposition's weak stroke-makers, and net bowling matchups individually - skipping just one item, like ignoring the wind direction for swing bowling, can undermine an otherwise well-planned strategy.
TLS and Information Disclosure
server_tokens off removes the Nginx version number from the Server response header and default error pages, denying attackers an easy fingerprint for matching known CVEs to your exact version. On the TLS side, ssl_protocols should be restricted to TLSv1.2 and TLSv1.3 only, ssl_prefer_server_ciphers should be off when using TLS 1.3's modern cipher negotiation (the server no longer needs to dictate cipher order the way it did under TLS 1.2), and weak legacy ciphers should be explicitly excluded via ssl_ciphers.
Cricket analogy: It's like a bowler refusing to reveal their exact variation before delivery through their run-up or grip, denying the batsman the easy tell that would let them pre-plan the shot.
# /etc/nginx/nginx.conf (http block)
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_session_tickets off;
client_max_body_size 2m;
client_body_buffer_size 128k;
large_client_header_buffers 4 8k;
user nginx nginx; # never run workers as rootRun nginx -V to see which modules were compiled in, and remove any you don't actually use (e.g., autoindex, unused third-party modules) at build time to shrink the attack surface.
Process, File Permissions, and Default Pages
The master process must start as root to bind privileged ports, but worker processes - the ones actually parsing untrusted client input - should run as a dedicated unprivileged user set via the user directive, so a worker compromise doesn't hand an attacker root. Default installation artifacts like the stock welcome page, autoindex directory listings on sensitive paths, and world-writable config or log directories should all be removed or locked down, since they're exactly the kind of low-effort finding an automated scanner picks up first.
Cricket analogy: It's like a team captain (root) making the toss decision but handing the actual bat and ball to specialist players (workers) for the risky part of the game, so a dismissal doesn't cost the whole team's leadership.
Leaving server_tokens on (the default) or the default Nginx welcome page in place is one of the most common findings in penetration tests - both are trivial for an automated scanner to detect and both hand an attacker free reconnaissance information.
- server_tokens off removes version disclosure from response headers and default error pages, denying attackers easy CVE-matching reconnaissance.
- ssl_protocols should be restricted to TLSv1.2 and TLSv1.3, with weak legacy ciphers explicitly excluded via ssl_ciphers.
- Worker processes should run as a dedicated unprivileged user (the user directive), never as root, limiting the blast radius of a worker compromise.
- client_max_body_size and buffer size directives should be tuned to realistic limits to prevent oversized request abuse.
- Default installation artifacts (stock welcome page, autoindex on sensitive directories) should be removed since they're common automated-scanner findings.
- Unused compiled-in modules increase attack surface and should be removed at build time where practical.
- Hardening is cumulative - a single overlooked directive like leaving server_tokens on can undo the benefit of an otherwise solid configuration.
Practice what you learned
1. What does server_tokens off accomplish?
2. Why should worker processes run as an unprivileged user rather than root?
3. Which TLS versions should generally remain enabled on a hardened Nginx server in 2026?
4. Why is the default Nginx welcome page considered a hardening risk?
5. What is a benefit of removing unused compiled-in Nginx modules at build time?
Was this page helpful?
You May Also Like
Nginx Security Headers
Learn how to configure HTTP response headers in Nginx to protect users from clickjacking, MIME-sniffing, XSS, and insecure transport.
Restricting Access with Nginx
Control who can reach specific routes and resources using Nginx's IP allow/deny rules, HTTP basic auth, and satisfy directives.
Nginx and Let's Encrypt
Automate free, trusted TLS certificates for Nginx using Let's Encrypt and Certbot, including auto-renewal and HTTPS redirection.
Mitigating DDoS with Nginx
Use Nginx's rate limiting, connection limiting, and buffering controls as a first line of defense against volumetric and application-layer DDoS attacks.
Related Reading
Related Study Notes in DevOps
Browse all study notesAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics