Security & Rate Limiting
Security & Rate Limiting
A publicly exposed Nginx front-end is the first line of defense for everything behind it. No matter how hardened your application code is, a misconfigured web server leaks version strings to scanners, accepts unlimited request floods, and passes attacker-controlled headers straight to your backend. This lesson covers the four pillars that production teams at large-scale companies treat as non-negotiable: rate limiting, response header hardening, version/fingerprint suppression, and basic WAF-style filtering.
Rate Limiting with limit_req
Nginx implements the leaky-bucket algorithm via the ngx_http_limit_req_module (compiled in by default). You define a shared-memory zone that tracks request rates per key — almost always $binary_remote_addr (the client IP in binary form, 4 bytes per IPv4 address, 16 per IPv6, saving memory vs the string form).
The two directives that work together are:
limit_req_zone— declared in thehttpblock, defines the key, zone name, shared memory size, and the target rate.limit_req— applied inside aserverorlocationblock, activates the zone and optionally allows aburstqueue andnodelayprocessing.
nodelay, burst requests are held in queue and drip out at the defined rate — this adds latency but is invisible to the client. With nodelay, burst requests are served immediately but consume burst slots; once the burst is exhausted, excess requests get a 429. Use nodelay for APIs where latency matters; omit it for rate-sensitive endpoints like auth where you actually want the delay.
In high-traffic deployments running behind a load balancer, $binary_remote_addr will be your LB's IP — every client looks the same. Use $http_x_forwarded_for or $http_x_real_ip instead, but only after validating trusted proxy IPs with set_real_ip_from and real_ip_header — otherwise any client can spoof the header and bypass rate limits entirely.
Response Header Hardening
Modern browsers trust response headers as security policy directives. Sending the right headers is one of the highest-leverage, lowest-cost security improvements you can make — a one-line config change that closes entire categories of attack.
always flag matters. Without it, Nginx only adds headers to 2xx responses. Error pages (4xx, 5xx) — which browsers also render — skip the headers. With always, every response gets the protection. Use it unconditionally.
Hiding Nginx Version and Fingerprints
By default, Nginx announces its exact version in the Server response header and in all error pages: nginx/1.24.0. Attackers use this to look up known CVEs and craft targeted exploits before you have time to patch. Suppressing version information is not security-through-obscurity — it meaningfully raises the cost of automated scanning.
server_tokens off, the default Nginx error page HTML contains the word "nginx". Use custom error pages to remove this final fingerprint for the highest-security environments.
Basic WAF-Style Filtering in Nginx
A full WAF like ModSecurity (as an Nginx module) or AWS WAF sits in front of Nginx. But before you reach for those tools, Nginx's own map, if, and geo directives can block a significant percentage of automated attack traffic with zero external dependencies.
Block common attack patterns by URI:
if inside location blocks. Nginx's if directive has subtle, dangerous semantics inside location contexts (the "if is evil" problem). Keep if at the server block level, or use map + return patterns which are evaluated in the safer rewrite phase. For complex WAF rules, use ModSecurity or a dedicated edge WAF — do not build a regex labyrinth in Nginx config.
Geo-blocking with the geo module: If your application legitimately serves only specific regions, the built-in ngx_http_geo_module can block entire IP ranges without an external tool. For country-level blocking, MaxMind GeoIP2 integrates via ngx_http_geoip2_module (packaged separately).
Putting It All Together: Hardened Server Block Pattern
Production teams apply these controls at the http block level in a shared include file (/etc/nginx/conf.d/security.conf) so every virtual host inherits them without repetition. Site-specific overrides go in the individual server block.
curl -I https://yourdomain.com locally, and use securityheaders.com and Mozilla Observatory in CI to gate deployments on a minimum security score. Google and large-scale SaaS teams run these checks as part of every release pipeline.