Request rate limiting offers significant utility in Nginx environments, yet it frequently suffers from misconfiguration due to misunderstandings regarding its operation. This feature allows administrators to constrain the frequency of HTTP requests from specific clients within a defined timeframe. These constraints apply universally, whether handling simple GET requests for static assets or POST submissions from authentication forms.
Beyond performance management, this mechanism serves critical security functions. By throttling request velocity to match legitimate user patterns, it impedes brute-force password attacks and mitigates distributed denial-of-service (DDoS) attempts. Primarily, it protects upstream application servers from being overwhelmed by excessive concurrent traffic.
Core Mechanism: The Leaky Bucket
Nginx employs the leaky bucket algorithm to manage traffic flow. Originally designed for networking packet switching, this model handles bursts effectively within bandwidth constraints. The analogy involves a bucket with an inlet and a draining hole at the base:
- Inlet: Incoming client requests.
- Bucket Body: A queue managed via First-In-First-Out (FIFO) scheduling.
- Base Drain: Requests processed by the server.
- Overflow: Requests discarded when capacity is exceeded.
If the inflow surpasses the drain rate, water spills (requests are dropped). If the inflow matches the drain, processing remains steady. When inflow exceeds capacity beyond the queue buffer, excess packets are rejected.
Basic Configuration
Two directives govern this behavior: limit_req_zone establishes parameters, while limit_req activates enforcement in specific contexts.
Zone Definition
Place this in the http block to define memory usage and tracking keys:
limit_req_zone $binary_remote_addr zone=auth_control:10m rate=20r/s;
Key parameters include:
- Key:
$binary_remote_addrstores state per IP efficiently. - Zone:
auth_control:10mcreates a shared memory region (approx. 5MB for 50k IPs) accessible across worker processes. - Rate:
rate=20r/ssets a theoretical limit of 20 requests per second, equivalent to one every 50ms.
Enforcement
Activate limits inside a server or location block:
server {
location /secure/api/ {
limit_req zone=auth_control;
proxy_pass http://backend_app;
}
}
Without additional tuning, exceeding 20 requests/second triggers immediate rejection (HTTP 503) for any request arriving before the previous one's 50ms window closes.
Managing Traffic Bursts
Real-world applications often experience sudden spikes. Strictly dropping these requests degrades user experience. To buffer temporary surges, utilize the burst parameter.
location /secure/api/ {
limit_req zone=auth_control burst=50;
proxy_pass http://backend_app;
}
Here, if a client sends requests faster than the rate allows, up to 50 extra requests enter a queue. They are processed sequentially once space opens up (every 50ms). Only if the queued count exceeds 50 will Nginx return an error.
Controlling Latency with Nodelay
Queuing introduces visible latency. For the 50th request in a queue, users might wait several seconds. The nodelay argument modifies how queued items are handled.
location /secure/api/ {
limit_req zone=auth_control burst=50 nodelay;
proxy_pass http://backend_app;
}
When nodelay is active, Nginx immediately forwards the first available batched request rather than queuing it all up. It marks slots as occupied but returns the response instantly if a slot was technically available at that exact moment. Slots are released only after the configured interval passes.
Example scenario with burst=50: If 51 requests arrive simultaneously, all 51 proceed immediately, marking 50 slots busy. Any further requests (52nd+) during the hold period face rejection. This ensures smooth communication without artificial delays while maintaining the aggregate rate cap.
Whitelisting via Geo and Map
To exclude trusted internal networks from restrictions, combine geo and map directives to manipulate the limiting key dynamically.
geo $bypass_limit {
default 0;
172.16.0.0/12 1;
10.0.0.0/8 1;
}
map $bypass_limit $dynamic_key {
1 "";
0 $binary_remote_addr;
}
limit_req_zone $dynamic_key zone=general_limits:25m rate=15r/s;
In this setup, IPs in private ranges set $bypass_limit to 1. The map directive converts this to an empty string, disabling checks for those zones. External IPs receive their address as the key, enforcing the rate limit of 15r/s.
Stacking Multiple Limits
A single location can host multiple limit_req instructions. The system applies the most restrictive rule among matching zones.
http {
limit_req_zone $binary_remote_addr zone=broad_limit:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=strict_limit:10m rate=5r/s;
server {
location /api {
limit_req zone=broad_limit burst=20 nodelay;
limit_req zone=strict_limit burst=5 nodelay;
}
}
}
An external attacker matching the stricter 5r/s limit will be throttled regardless of the broader 100r/s allowance. Internal whitelisted IPs bypass the check entirely based on the earlier geo logic.
Logging Configuration
Requests that are delayed or dropped generate log entries. Default severity depends on the action; delayed usually logs at info level, while drops log at error.
limit_req_log_level warn;
location /secure/api/ {
limit_req zone=auth_control burst=50 nodelay;
}
Sample log output indicates the overflow details:
2023-10-27T12:00:00Z [warn] ... limiting requests, excess: 5.000 by zone "auth_control", client: 203.0.113.5
Fields captured include the excess count per millisecond, zone name, client IP, server hostname, and the requested URI.
Custom Error Responses
The standard rejcetion status is 503 (Service Temporarily Unavailable). You may customize this using limit_req_status.
location /secure/api/ {
limit_req zone=auth_control burst=50 nodelay;
limit_req_status 429;
}
This returns HTTP 429 (Too Many Requests), which is more descriptive for API consumers.
Total Blocking vs Rate Limiting
If complete access suppression is required for a path, do not use rate limiting. Instead, utilize explicit deny rules.
location /forbidden/path/ {
deny all;
}
This instructs Nginx to reject connection immediately rather than tracking and throttling the request volume.