Nginx Rate Limiting Configuration: Core Setup

by Liam Foster
Nginx Rate Limiting Configuration: Core Setup

Nginx rate limiting configuration protects your backend by rejecting excess requests before they consume resources. The mechanism is simple: define a zone (shared memory bucket), set a rate in requests per time unit, and apply it to a location or server block.

Rate limiting is not authentication or access control—it's a traffic valve. It stops a single client from overwhelming your service, whether by accident (a runaway script) or intent (a DDoS probe). Unlike firewalls, it works at the application layer and understands HTTP semantics.

The Rate Limiting Model

Nginx rate limiting operates in two stages:

  1. Zone definition — declare a shared memory area with a name, key (usually client IP), and rate.
  2. Application — attach that zone to a location, server, or upstream with limit_req.

The zone is the container; the limit_req directive is the gate. Think of the zone as a per-key counter that increments on each request and leaks at a fixed rate (like water draining from a bucket).

http {
  limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
  
  server {
    listen 80;
    server_name api.example.com;
    
    location /api/ {
      limit_req zone=api_limit burst=20 nodelay;
      proxy_pass http://backend;
    }
  }
}

Break down each parameter:

  • $binary_remote_addr — the key (client IP in binary form, more efficient than string).
  • zone=api_limit:10m — zone name and size in memory. 10m holds roughly 160,000 keys on a 64-bit system.
  • rate=10r/s — sustained rate: 10 requests per second.
  • burst=20 — queue size. Allows 20 requests above the rate to queue without rejection.
  • nodelay — process queued requests immediately instead of spreading them across time.

How It Works in Practice

When a request arrives, nginx looks up the client IP in the zone's counter. If the counter is below the rate limit, the request is allowed and the counter increments. If the counter exceeds the rate, the request either queues (if burst capacity remains) or is rejected with a 503 Service Unavailable.

Without nodelay, queued requests are delayed to spread them evenly over time—useful for smoothing traffic spikes. With nodelay, all queued requests are forwarded immediately once the zone allows it, which is faster but may still overwhelm a slow backend.

The zone memory decays over time. If a client goes silent for a few seconds, their counter resets toward zero. This is automatic and requires no cleanup.

Key Variations

By HTTP method or URI parameter:

limit_req_zone $binary_remote_addr$request_uri zone=per_endpoint:10m rate=5r/s;

This zones each endpoint separately, so a client hitting /api/users at 5 r/s and /api/posts at 5 r/s is allowed, but /api/users alone at 10 r/s is throttled.

By username (authenticated requests):

limit_req_zone $http_x_user_id zone=by_user:10m rate=100r/s;

Useful for SaaS platforms where you want per-user limits regardless of IP.

Multiple zones with different thresholds:

http {
  limit_req_zone $binary_remote_addr zone=general:10m rate=100r/s;
  limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
  
  server {
    location /api/ {
      limit_req zone=api burst=5;
      proxy_pass http://backend;
    }
    location / {
      limit_req zone=general burst=50;
      proxy_pass http://backend;
    }
  }
}

When This Breaks

Shared infrastructure behind a load balancer: If nginx instances don't share zone state, each instance enforces the limit independently. A client hitting a round-robin load balancer can multiply their effective rate. Solution: either use sticky sessions, centralize rate limiting upstream (at the LB or API gateway), or accept the looser enforcement.

Zone memory exhaustion: A zone sized at 10m can hold ~160k keys. If you have 500k unique clients, collisions occur and the oldest entries are evicted. Monitor zone usage with nginx -T | grep limit_req_zone and size accordingly. Each key consumes roughly 64 bytes.

Burst without nodelay starves other clients: If you set burst=1000 without nodelay, a single aggressive client can queue 1000 requests, and nginx will slowly release them. Meanwhile, legitimate traffic is delayed. Use burst sparingly (10–50) or enable nodelay.

Interaction with upstream keepalive: If your backend is slow, rate-limited requests queue in nginx but hold open TCP connections to the backend. This can exhaust backend connection limits. Pair rate limiting with a reasonable proxy_connect_timeout and proxy_read_timeout. If you're also evaluating infrastructure choices for high-traffic workloads, the comparison on tinjauhost.biz.id covers hosting options worth considering alongside your nginx tuning.

False positives on shared IPs: Corporate proxies, mobile carriers, and cloud egress points share IPs. Rate limiting by $binary_remote_addr alone punishes all users behind that IP. For public APIs, consider combining IP-based limits with a lighter token-bucket scheme (e.g., API key limits) or user-agent fingerprinting.

Trade-Offs

Accuracy vs. memory: Binary IP ($binary_remote_addr) uses less memory than string IP ($remote_addr), but string keys are more flexible (you can combine IP + user-agent, for example).

Strictness vs. user experience: A low burst (e.g., burst=5) rejects traffic quickly but may reject legitimate spikes. A high burst (e.g., burst=100) tolerates spikes but delays slow clients longer.

Per-endpoint vs. per-client: Zoning by URI allows fine-grained control but multiplies zone memory use. Zoning by IP is simpler but treats all endpoints as one pool.

Monitoring and Tuning

Enable the ngx_http_limit_req_module status page (requires stub_status or a metrics exporter) to observe zone hit rates and rejections. Look for:

  • Rejection count (limit_requests_rejected): If it spikes during normal traffic, your rate is too tight.
  • Zone memory pressure: If zone size approaches capacity, increase it or reduce key cardinality.
  • Burst queue depth: If queued requests consistently max out, increase burst or lower the rate.

Start conservative (e.g., rate=100r/s, burst=20) and relax based on observed traffic patterns. A rate set too high defeats the purpose; too low wastes the feature. If your team is also working on discoverability, konsultasi SEO online can help align your infrastructure decisions with broader web performance goals.

One-Line Takeaway

Nginx rate limiting is a memory-efficient, per-zone traffic valve: define the zone with a key and rate, apply it with limit_req and burst, and monitor rejection counts to tune the parameters for your traffic profile and backend capacity.