Skip to main content

Rate Limiting

Rate limiting restricts how many requests a client can make to your server within a defined time window.

In NGINX, this is implemented using the limit_req module, which protects against:

  • Brute-force login attacks
  • DDoS and HTTP flood attacks
  • API abuse
  • Excessive bot traffic
  • Resource exhaustion

How limit_req Works (Internals)

NGINX uses the leaky bucket algorithm:

  1. Each client (IP, user, token, etc.) has a “bucket”
  2. Requests add water to the bucket
  3. Water leaks out at a fixed rate (rate)
  4. If the bucket overflows → request is delayed or rejected

Key idea:

Requests are allowed at a steady rate, with controlled bursts.

Core Components of limit_req

limit_req_zone (Define the Rate Limit)

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
PartMeaning
$binary_remote_addrClient identifier (IP address)
zone=api_limit:10mShared memory zone (10 MB)
rate=10r/s10 requests per second per client

10 MB ≈ 160,000 unique IPs

limit_req (Apply the Limit)

limit_req zone=api_limit burst=20 nodelay;
OptionPurpose
zoneWhich rate limit to apply
burstAllow short traffic spikes
nodelayReject excess immediately

Basic Example: Global Rate Limiting

http {
limit_req_zone $binary_remote_addr zone=global:10m rate=5r/s;

server {
listen 80;

location / {
limit_req zone=global burst=10;
proxy_pass http://backend;
}
}
}
  • 5 requests/sec allowed per IP
  • Up to 10 extra requests queued
  • Excess requests are delayed

Strict Security Example: Block Bruteforce Logins

limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;

server {
listen 443 ssl;

location /login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://auth_backend;
}
}
  • Max 1 request per second
  • Burst of 3 attempts
  • Excess requests → HTTP 503

Define Limit per Client

limit_req_zone $binary_remote_addr zone=api:20m rate=20r/s;

Apply to API Endpoints

location /api/ {
limit_req zone=api burst=40 nodelay;
proxy_pass http://api_backend;
}

Result:

  • 20 requests/sec
  • Burst up to 40
  • Hard limit beyond burst

Using Custom Keys (More Secure)

Rate Limit by API Key Header

limit_req_zone $http_x_api_key zone=api_key:10m rate=10r/s;

If header is missing → all requests share the same bucket

  • Useful for authenticated APIs
  • Better than IP-based limiting

Burst vs No Burst (Security Impact)

ConfigBehavior
burst=0Hard limit
burst=10Allows short spikes
burst=10 nodelayReject excess immediately
limit_req zone=api burst=10 nodelay;

Best for security-critical endpoints

Custom Error Response for Rate Limit

limit_req_status 429;

location /api/ {
limit_req zone=api burst=20;
}

Returns HTTP 429 Too Many Requests

Combining Rate Limiting with HTTPS

server {
listen 443 ssl;

ssl_certificate /path/fullchain.pem;
ssl_certificate_key /path/privkey.pem;

location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
}

Common Security Mistakes

MistakeRisk
Using $remote_addr instead of $binary_remote_addrHigher memory usage
No rate limiting on /loginBrute-force vulnerability
Too high burstIneffective protection
No limit_req_statusPoor monitoring
IP-only limits behind proxyAll users share one bucket

Rate Limiting Behind Load Balancers

Fix Real Client IP

set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;

Then use:

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

Testing Rate Limiting

Using curl

for i in {1..20}; do curl -I https://example.com/login; done

Expected:

  • Initial success
  • Then 429 or 503 errors

limit_req vs limit_conn

Featurelimit_reqlimit_conn
LimitsRequest rateConcurrent connections
Use caseAPIs, loginWebSocket, slow clients
AlgorithmLeaky bucketHard counter