Rate limiting cheatsheet.
limit_req_zone
http {
limit_req_zone $binary_remote_addr zone=ip:10m rate=10r/s;
limit_req_zone $http_x_api_key zone=api:10m rate=100r/s;
}
server {
location / {
limit_req zone=ip burst=20 nodelay;
proxy_pass http://app;
}
}
rate=10r/s: 10 requests per second per key.burst=20: allow burst of 20 queued requests.nodelay: don’t delay queued requests (just reject when full).10m: zone size; ~160k IPs.
Multiple zones
location /api/ {
limit_req zone=ip burst=20 nodelay;
limit_req zone=api burst=200 nodelay;
}
Multiple zones can apply.
limit_conn
limit_conn_zone $binary_remote_addr zone=conn_ip:10m;
server {
limit_conn conn_ip 10;
}
Max 10 simultaneous connections per IP.
Strict vs lenient
# Strict: drop excess (default)
limit_req zone=ip burst=20;
# Lenient: queue then nodelay
limit_req zone=ip burst=20 nodelay;
Custom status code
limit_req_status 429;
limit_conn_status 429;
Default 503.
Different limits per location
location /login {
limit_req zone=ip burst=5 nodelay;
limit_req_status 429;
}
location /api {
limit_req zone=api burst=100 nodelay;
}
location / {
limit_req zone=ip burst=50 nodelay;
}
Strict for login (brute force), looser for general.
Geo / map based limits
geo $limit {
default 1;
10.0.0.0/8 0; # internal IPs not limited
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=ip:10m rate=10r/s;
Empty key = not limited.
Log slow requests
log_format main '... $request_time';
awk '$NF > 1.0' /var/log/nginx/access.log # > 1s requests
Whitelist / allowlist
geo $whitelist {
default 0;
192.168.1.0/24 1;
10.0.0.0/8 1;
}
map $whitelist $limit_key {
0 $binary_remote_addr;
1 "";
}
limit_req_zone $limit_key zone=ip:10m rate=10r/s;
limit_rate (bandwidth)
location /downloads/ {
limit_rate 100k;
limit_rate_after 10m; # full speed first 10MB
}
Or based on connections:
limit_conn_zone $binary_remote_addr zone=conn:10m;
limit_conn conn 1;
limit_rate 500k;
Cookie-based rate
map $cookie_session $rate_key {
default $cookie_session;
"" $binary_remote_addr;
}
limit_req_zone $rate_key zone=session:10m rate=20r/s;
Logged-in users limited per session, anon per IP.
Higher rate for authenticated
limit_req_zone $http_authorization zone=auth_high:10m rate=1000r/s;
limit_req_zone $binary_remote_addr zone=anon_low:10m rate=10r/s;
map $http_authorization $rate_zone {
"" anon_low;
default auth_high;
}
# Pass zone via map (limit_req can't be variable; use if blocks or two locations)
In practice: route by auth status into different locations.
Per-endpoint differentiation
location = /api/expensive {
limit_req zone=ip burst=2 nodelay;
}
location /api/ {
limit_req zone=ip burst=50 nodelay;
}
Distributed rate limiting
Nginx open-source: per-instance only. For cluster-wide:
- Upstash Ratelimit / similar in app.
- Redis-based with OpenResty (
resty.limit.req). - Move to mesh-level rate limiting (Istio).
Lua / OpenResty rate limit (Redis-backed)
location / {
access_by_lua_block {
local limit = require("resty.limit.req").new("my_limit", 100, 50)
local key = ngx.var.binary_remote_addr
local delay, err = limit:incoming(key, true)
if not delay then
if err == "rejected" then ngx.exit(429) end
ngx.log(ngx.ERR, err); ngx.exit(500)
end
}
proxy_pass http://app;
}
Common mistakes
$remote_addrinstead of$binary_remote_addr— wastes memory.- Too aggressive rate, blocks legit users.
- No whitelist for internal services / healthchecks.
- Same zone for static + API → static eats budget.
- Forgetting
burst→ no buffer for spikes.
Read this next
If you want my rate-limit + whitelist setup, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .