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 $binary_remote_addr zone=req_limit:10m rate=10r/s; location /api/ { limit_req zone=req_limit burst=20 nodelay; proxy_pass http://backend_pool; }

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_zone $binary_remote_addr zone=conn_limit:10m; location /downloads/ { limit_conn conn_limit 5; }

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.

DirectiveProtects against
limit_reqToo many requests per second from one key
limit_connToo many simultaneous open connections from one key
burst/nodelayTuning how short traffic spikes are handled
A real, practical security control
Rate limiting is one of the most direct, practical defenses against brute-force login attempts and basic scraping/DoS traffic — directly relevant to Authentication & Session Security's own material on protecting login endpoints. A tight limit_req specifically on a login path is a genuinely useful, low-effort layer of defense against automated credential-stuffing attempts.
Rate limiting at Nginx is one layer, not a complete DoS defense
It's tempting to treat Nginx-level rate limiting as a complete defense against denial-of-service traffic. It genuinely protects against one or a few IPs overwhelming a single Nginx instance — but it does nothing against a truly distributed attack spread across many real, different IP addresses, each staying comfortably under the per-IP limit, and nothing against traffic that never reaches Nginx at all (a sufficiently large volumetric attack can saturate network capacity before Nginx's own rate limiting ever gets a chance to act). It's also, per this chapter's own key-selection section, subject to the same shared-IP/NAT caveat as 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

Exercise 1

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 solution
Exercise 2

A 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 solution
Exercise 3

A 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 solution

Chapter 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