Rate Limiting & Connection Limiting
Nginx In Depth
Chapter 8 · Rate Limiting & Connection Limiting
Chapter 4's own max_fails mechanism only reacts after a backend has already started failing. This chapter covers a genuinely different, proactive protection: limiting how much traffic any single client can send in the first place, before a backend is ever put under real strain.
limit_req_zone & limit_req
limit_req_zone, set at the http {} level, defines a shared-memory zone tracking request rate per key — here, per client IP ($binary_remote_addr), capped at 10 requests per second. limit_req then applies that zone inside a specific location. burst=20 allows up to 20 requests above the steady rate to be queued rather than immediately rejected, absorbing short traffic spikes; nodelay processes those burst requests immediately instead of queuing/delaying them, at the cost of allowing a brief burst above the strict steady rate.
limit_conn_zone & limit_conn
limit_conn is a distinct mechanism from limit_req: rather than capping requests-per-second over time, it caps how many simultaneous open connections a single key (again, commonly the client IP) can hold at once — useful for preventing one client from monopolizing a meaningful share of the available worker connections (Chapter 2), for example by opening many concurrent large file downloads at once.
Choosing a Key
$binary_remote_addr — the client's IP in binary form, more memory-efficient than the plain-text IP — is the most common key for both directives. The same caveat from Chapter 5's own ip_hash material applies here too: many real, distinct users sharing one visible IP address (behind a large corporate NAT) will all share the identical rate or connection limit, since the key can't distinguish between them.
What Happens When the Limit Is Hit
By default, a request exceeding either limit receives an HTTP 503 Service Unavailable — customizable via limit_req_status/limit_conn_status. The burst+nodelay combination specifically changes this behavior: without nodelay, requests within the burst allowance are queued and processed with an added delay rather than rejected outright, which can look like the site simply slowed down rather than an explicit block — worth knowing when diagnosing "why did this request take longer than expected" rather than assuming rate limiting only ever produces an outright rejection.
| Directive | Protects against |
|---|---|
limit_req | Too many requests per second from one key |
limit_conn | Too many simultaneous open connections from one key |
burst/nodelay | Tuning how short traffic spikes are handled |
limit_req specifically on a login path is a genuinely useful, low-effort layer of defense against automated credential-stuffing attempts.
ip_hash — a rate limit applied by IP can accidentally throttle an entire legitimate corporate office sharing one address. Real protection combines this with other layers, not this alone.
Hands-On Exercises
Write the limit_req_zone and limit_req directives to limit a /login endpoint to 5 requests per second per client IP, allowing a burst of up to 10 requests processed without added delay.
📄 View solutionA team configures limit_req with burst=20 but no nodelay flag, then is confused when requests within that burst allowance still appear to succeed but take noticeably longer than usual, rather than being rejected outright. Explain why, using this chapter's own material.
📄 View solutionA company believes their Nginx-level rate limiting fully protects them from any denial-of-service attack. Using this chapter's own warning box, describe one realistic attack scenario this rate limiting would NOT stop.
📄 View solutionChapter 8 Quick Reference
- limit_req_zone/limit_req — caps requests per second per key; burst absorbs spikes; nodelay processes burst requests immediately instead of queuing them
- limit_conn_zone/limit_conn — caps simultaneous open connections per key, a distinct concern from request rate
- Both commonly key on $binary_remote_addr — subject to the same shared-IP/NAT caveat as Chapter 5's
ip_hash - Default rejection is a 503; without
nodelay, burst requests are delayed rather than rejected — a real source of "why did this slow down" confusion - A genuinely useful layer against brute-force/basic DoS traffic, but not a complete defense against a distributed attack or network-saturating traffic