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;
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_addr instead 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 .