7.3 Connections and Bandwidth: limit_conn, limit_rate
Requests aren't the only thing clients spend. Some spend connections; some spend bytes. Two directives cap each — measured both ways, with receipts.
Intermediate
What you will learn
limit_conn— concurrency caps per key, and why it's the right tool against slowloris-shaped abuse.The measured demo: three connections, cap of two, live 503.
limit_rate— bandwidth throttling per connection, measured against a real download.The composition math:
limit_rate_after,limit_conn+limit_req, and per-location scoping.What these directives can't do — and the Layer-4 truth (Module 10 preview).
1.
limit_conn — capping concurrency
limit_conn_zone $binary_remote_addr zone=conns:10m; # creator (http)
location /conn/ {
limit_conn conns 2; # consumer: max 2 SIMULTANEOUS per key
limit_conn_status 503; # (or 429 — see §4)
…
}Same creator/consumer shape as
limit_req, but the counted thing is
connections currently in flight through this location, per key. Request finishes → slot frees. It's not about
rate at all — a client may make 100 requests/second through 1 connection and never trip it.
The workload it defends: long-held, concurrent connections — progress-bar downloads, websocket feeds, and the hostile versions (slowloris hold-open, connection-flood scrapers). The condition, manufactured honestly: two concurrent requests to the slow backend (2 s each), then a third:
$ curl …/conn/ & # occupies slot 1 (slow backend, ~2s)
$ curl …/conn/ & # occupies slot 2
$ sleep 0.5
$ curl -w '%{http_code}\n' …/conn/
503 # limit_conn 2: the third concurrent connection refused(Background jobs finish; slots free; the next request succeeds — try it after the
wait.)
Two facts that shape real deployments:
The count is per-zone, enforced where a consumer lives — a
limit_connin/downloads/doesn't police/api/. To cap a client fleet-wide, put the consumer athttplevel (inherited everywhere, Lesson 2.5's ladder).Keys compose with identity exactly like Lesson 7.2: per-IP default, per-token for APIs, per-
$arg_keywhere trust allows.
2.
limit_rate — capping bytes
location /speed/ {
limit_rate 128k; # 131072 bytes/sec, PER CONNECTION
}Serving the 256 KB fixture through that location, measured:
$ curl -o /dev/null -w 'speed=%{speed_download} time=%{time_total}s\n' \
-H 'Host: edge.example.test' http://127.0.0.1:8700/speed/256k.bin
speed=131071 time=2.00s # limit_rate 128k: throughput pinned right at the ceilingThe download took exactly the 2 seconds that 262 144 ÷ 131 072 implies — the cap enforced with kernel-timer precision (NGINX paces
write() calls; the client's TCP window does the rest).
The three refinements that make it production-grade:
limit_rate_after 1m;— the first megabyte at full speed, then the cap: instant-feeling previews, sustainable bulk. The streaming service's favorite directive.Per-connection, not per-client: a client opening 6 connections gets 6× the cap — combine with
limit_conn(orlimit_req) when that matters. This is the classic composition:
limit_conn conns 2; # no parallel-download multiplication
limit_rate 256k;proxy_download_rate— not an HTTP directive —proxy_download_rate/proxy_upload_rateare the stream module's Layer-4 twins (Module 10); on the HTTP sidelimit_rateis the only pacer, and it governs NGINX→client bytes regardless of origin.
3. The composition math
The three limiters are orthogonal axes — pick per workload:
Workload | rate (requests) | conn | rate (bytes) |
|---|---|---|---|
JSON API | ✅ per-token bucket | (rarely) | — |
File downloads | — (requests are rare) | ✅ 1–2 | ✅ +
|
WebSockets | — (one request!) | ✅ | — |
Login form | ✅ strict (no burst) | — | — |
Stacking
limit_req
+
limit_conn is normal and each evaluates independently — a request must pass
every applicable gate. The example's
/conn/ has no
limit_req, which is why 20 rapid
sequential requests never trip anything: connections are 1-at-a-time.
4. Status codes and the 429 question
limit_conn_status defaults to 503 — arguably wrong for "you have too many connections" (not a service outage). 429 is the modern choice for both limiters; the example sets
limit_req_status 429 but leaves
limit_conn at 503
deliberately, so §1's 503 output teaches you the default exists. Pick per fleet — and know that monitoring
must distinguish "edge refused" (429/503 with
$limit_conn_status: REJECTED in the log) from "backend down" (503 with empty
$upstream_addr) — one log format, two very different pages.
5. What these can't do
Honest limits, stated once:
limit_ratecan't distinguish clients' fairness beyond connections — no per-user byte budgets, no daily quotas. Those live in apps or metering systems (or njs, Module 15, counting bytes per identity).limit_conncounts this NGINX's view. Behind an LB, or with multiple NGINX nodes, each counts its own connections — a fleet-wide cap of 2 per client needs the LB (or Module 10's stream layer, or Module 13's shared-state patterns).Layer-4 floods (SYN, UDP amplification) never reach the HTTP limiters at all — that's Module 10's territory (and the kernel's), and pretending otherwise is how incidents happen.
Try it yourself
The §1 experiment — three concurrent, third refused; then
waitand succeed. Feel the slot lifecycle.Measure the cap with curl's own telemetry (§2's exact command) — then add
limit_rate_after 64k;and watch the first quarter of the file arrive instantly.Break the cap with parallelism (§2's footnote):
seq 1 4 | xargs -P4 -I{} curl -s -o /dev/null …/speed/256k.bin— total throughput ≈ 4× the cap. Now addlimit_conn conns 2to/speed/and watch two of the four fail: composition in one edit.Fleet-wide vs local. Move the
limit_connconsumer to http level (outside the server), reload, and verify a cap in/api/counts connections in/speed/too.
Q&A
Q1. A client makes 200 requests/minute through a single keep-alive connection.
limit_req rate=10r/s and
limit_conn 5 — what trips?
Answer
Neither, on this evidence: 200/min ≈ 3.3 r/s is under the 10 r/s bucket, and one connection is far under the cap of 5. The limiters measure different things — you'd trip
limit_req by bursting past 10/s, or
limit_conn by holding 6 simultaneous connections.
Q2. Why do streaming sites pair
limit_rate_after with
limit_rate?
Answer
UX + economics: the first N bytes at full speed make playback start instantly (and feel "fast"), while the sustained cap keeps per-user bandwidth — and transit bills — bounded for the long tail of the transfer.
Q3. Six parallel downloads each get the 256 KB/s cap — why, and what's the two-directive fix?
Answer
limit_rate is per
connection; parallelism multiplies client throughput. Fix:
limit_conn <zone> 1..2 (cap concurrency) so the per-connection cap can't be multiplied — §2's footnote, demonstrated live.
Q4. Your three-node NGINX fleet needs "max 2 concurrent downloads per user, globally." Can
limit_conn do it alone?
Answer
No — each node counts only its own connections; a user spread across nodes gets 2 per node. The global cap must live at the shared layer: the LB, a stream-tier proxy (Module 10), or app-side metering.
Takeaways
limit_conncaps simultaneous per-key connections — the defense for long-lived and hostile hold-open patterns; slots free on completion.limit_ratepaces bytes per connection with timer precision (§2 measured 131 071 B/s against a 128k cap);limit_rate_afterbuys instant-feeling starts.The three limiters are orthogonal axes — requests, connections, bytes — composed per workload, never confused for each other.
Both are per-NGINX views: fleet-wide or L4 enforcement needs the shared layer that Module 10/13 provide.
Status-code and log discipline (
$limit_conn_status,$limit_req_status) keeps "edge refused" distinct from "service down" in your monitoring.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.