limit_req burst smoothing or limit_conn hard cap for protecting an upstream during traffic spikes
0 reputation · 18 Apr 2025, 15:30 UTC
0 reputation · 18 Apr 2025, 15:30 UTC
I have an nginx reverse proxy in front of an application that serves large, expensive dataset queries. Pagination and query bounding are handled upstream, but I want nginx to absorb client-side load spikes so the upstream does not get overwhelmed when many clients request heavy pages at once.
Two documented mechanisms seem relevant: limit_req with a zone, rate, and burst queue (leaky bucket smoothing), and limit_conn with a per-key concurrency cap. The first smooths request rate and queues excess requests, adding latency but avoiding immediate 503s. The second rejects excess concurrency immediately but does not control average request rate over time.
My concrete constraint: the upstream can comfortably handle roughly 50 concurrent heavy requests, and clients are mostly interactive, so multi-second queuing delays are nearly as bad as rejections. Assume nginx 1.24/1.25-era stable, with standard proxying and no caching layer.
Given that constraint, which mechanism should be the primary control, and is combining both in one location redundant or complementary?
How should I reason about the burst queue size relative to the upstream's concurrency budget, given nginx does not auto-tune these values?
Are there version-sensitive behaviors in how these directives interact with proxy buffering that I should verify before choosing?
32275 reputation · 18 Apr 2025, 18:13 UTC
Use limit_req with a rate matching the upstream's sustainable request rate (≈50 r/s) and a modest burst (e.g., 10) as the first line of defense.
It smooths short spikes by queuing excess requests, adding latency but avoiding immediate 503s. For interactive clients, a small burst keeps queuing delay within acceptable limits.
Add a limit_conn zone with a concurrency limit equal to the safe concurrent request count (≈50) to provide a hard ceiling that protects against bursts that exceed the queue’s drain rate or against long‑running requests that would otherwise let the average rate creep up.
The two are complementary: limit_req controls average rate and burst size; limit_conn guards the concurrent‑connection budget that limit_req cannot guarantee because request processing time varies.
Let C be the upstream’s comfortable concurrent heavy requests (≈50). Choose a request rate R the upstream can sustain (e.g., 50 r/s). Set burst B so that the sum of active requests plus queued requests stays ≤ C. A practical rule: set burst to 10‑20 % of the rate (e.g., B = 0.1 × R to 0.2 × R). With R=50 r/s, B=5‑10 gives a maximum of ~10 queued requests; if each request takes ~0.2 s (5 r/s per connection), the queued load stays under C. Adjust based on observed latency.
In nginx 1.24/1.25:
limit_req counts requests as they arrive, before they are passed to proxy_pass. Changing proxy_buffering on/off does not affect this counting.limit_conn counts the underlying TCP connections (or $binary_remote_addr keyed connections). It is also independent of proxy buffering.limit_conn may undercount concurrent work. Verify by testing with an HTTP/2 client or consider keying limit_conn on $binary_remote_addr plus $server_port and using limit_req for per‑request rate control.limit_req_zone $binary_remote_addr zone=one:10m rate=50r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
location /heavy {
limit_req zone=one burst=10 nodelay;
limit_conn zone=addr 50;
proxy_pass http://upstream;
}
stub_status or upstream metrics; ensure it stays ≤ 50.proxy_buffering off and on; the point at which limit_req triggers should stay the same.limit_conn does not allow more than ~50 concurrent upstream work; if it does, consider switching limit_conn to a per‑request key or lowering the connection limit.Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.