Candid photograph of a Linux server terminal with a soft olive-green glow against a dark slate background, conveying a calm technical atmosphere.

Step-by-step guides for system administrators — covering command-line basics, web server setup, and preparation material for technical interviews.

Browse Tutorials

Nginx rate limiting for layer 7 DDoS defense

Layer 7 distributed denial of service attacks are the most reported disruption to the Australian Cyber Security Centre, and small businesses running Nginx in front of WordPress or custom apps absorb request floods that saturate Telstra and Optus upstream links before the kernel sees a SYN. The attacks are cheap to launch and expensive to absorb because every byte counts toward a legitimate-looking request stream.

Nginx ships with limit_req and limit_conn, two directives that cap request rates per client and per connection. They are simple to configure, but a careless setup will lock out real customers the moment a campaign in Sydney goes viral. The remainder of this walk-through builds a policy from the first limit_req_zone line to a tested configuration suited to Australian hosting realities.

Understanding the core directives

The foundation is limit_req_zone, declared at the http level. It defines a shared memory zone, a key (almost always the client IP), and a rate in requests per second. A typical entry uses $binary_remote_addr because it is compact and avoids the slow path of string parsing, which matters when traffic peaks coincide with AEST business hours and your upstream logs rate alerts in UTC+10.

The companion directive, limit_req, lives inside a location or server block. It references the zone above and accepts parameters like burst and nodelay. Without them, Nginx returns a 503 the instant the bucket runs dry, brutal for any user who clicks twice. Treating the directives as a pair rather than independent settings is the difference between a usable web tier and a self-inflicted outage.

Building the first request zone

Start with a conservative zone for static assets:

limit_req_zone $binary_remote_addr zone=static:10m rate=30r/s;

Inside the location block serving images and CSS, apply:

limit_req zone=static burst=40 nodelay;

This allows a burst up to forty requests before throttling, keeping a busy browser from being penalised for loading many small resources. The 10 megabyte zone holds roughly 160,000 unique IPs, enough for a typical Australian e-commerce site whose visitors cluster around Sydney, Melbourne, and Perth. When sizing, remember that 1 megabyte stores about 16,000 IP addresses.

Layering limits with burst and nodelay

The burst parameter defines how many requests can queue once the rate limit is reached. The nodelay parameter serves those queued requests immediately rather than spacing them over the next second. Together they smooth spikes from legitimate clients without letting an attacker queue thousands of requests that will eventually fire at the backend.

For login endpoints, a tighter policy helps. A separate zone such as limit_req_zone $binary_remote_addr zone=auth:1m rate=5r/s; applied with limit_req zone=auth burst=10 nodelay; prevents credential stuffing while permitting a user to mistype several times in a row. When tuning, watch the Nginx error log after enabling limits; sustained "limiting requests" messages from Australian IP ranges usually mean the rate is too aggressive for the local user base, while spikes from unfamiliar geos point at bot activity that should move to a blocklist instead.

GeoIP and connection controls

Rate limiting by IP alone is not enough when attackers rotate through residential proxies. Pairing limit_req with the GeoIP module and the map directive tightens zones for traffic outside your primary markets, which in Australia usually means the .au ccTLD plus a handful of countries your business actually serves. The map directive can set a lower rate for unrecognised countries while leaving Australian IPs unrestricted.

The limit_conn directive complements limit_req by capping concurrent connections per IP, a useful guard against slow-loris style attacks that hold sockets open. A typical limit_conn_zone $binary_remote_addr zone=conn:10m; followed by limit_conn conn 20; inside the server block caps a single client at twenty simultaneous connections. Administrators who want to inspect the request handling pipeline in detail can learn more about selecting context objects for richer debugging, especially when distinguishing bot traffic from genuine customers.

Testing and monitoring the setup

Configuration drift is the silent killer of rate limiting policies. After reloading Nginx with nginx -t and systemctl reload nginx, run a synthetic load test from an Australian vantage point using ab, wrk, or hey. A simple wrk -t4 -c100 -d30s against a Sydney region endpoint reveals whether burst values are too tight before real users notice.

In production, watch the Nginx stub status module and system metrics. A spike in 503 responses combined with steady disk I/O usually means the backend is healthy and Nginx absorbs the attack, while growing disk activity points to logs filling up under load. Operators who want a deeper look at system bottlenecks will appreciate the guide on monitoring disk I/O, which walks through iostat and iotop for exactly this triage. Pair those checks with log shipping to a central SIEM and you have the feedback loop needed to tune rates as traffic patterns shift.

Practical recommendations for a resilient setup

A well-tuned Nginx rate limiting configuration pays for itself the first time a botnet points at your application, and the maintenance cost is measured in minutes per quarter rather than the hours of firefighting that follow a successful flood. Remember the core idea: a modest rate per IP, a generous burst, layered zones per endpoint, and continuous measurement are the pillars that keep a Layer 7 DDoS from becoming a headline on the local IT press.