How to Implement Nginx Rate Limiting: Prevent Abuse, Scraping, and API Overload

This article explains how to use Nginx's built‑in limit_req and limit_conn modules—based on the leaky‑bucket algorithm—to protect APIs from malicious flooding, excessive crawling, sudden traffic spikes, and individual user abuse, covering configuration directives, practical examples, advanced scenarios, testing, and deployment best practices.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
How to Implement Nginx Rate Limiting: Prevent Abuse, Scraping, and API Overload

Problem Background

In production environments, Nginx rate‑limiting is needed for scenarios such as malicious request bursts on login/registration APIs, aggressive web crawlers, traffic spikes from promotions, and single‑user overuse that can degrade service for others.

Core Principle: Leaky Bucket Algorithm

The limit_req module implements a leaky‑bucket algorithm. Key parameters are:

rate : the constant outflow rate (allowed requests per second).

burst : the bucket capacity that buffers short‑term spikes.

nodelay/delay : whether requests exceeding the burst are queued (delayed) or rejected immediately.

Compared with a token‑bucket, the leaky bucket enforces a strict output rate, while burst + nodelay can emulate token‑bucket behavior.

Configuration Directives Overview

limit_req_zone

(http scope): defines a shared memory zone, the key (e.g., $binary_remote_addr) and the request rate. limit_req (http/server/location): applies the defined zone, optionally with burst and nodelay. limit_req_status: custom response code for rejected requests (default 503, recommended 429). limit_req_log_level: log level for rejected or delayed requests. limit_req_dry_run (>= 1.17.1): dry‑run mode that only records statistics without actually rejecting.

Basic Configuration Examples

IP‑Based Limiting (most common)

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

Effect:

Each IP can make up to 10 requests per second.

Up to 20 additional requests are processed immediately (burst).

Requests beyond 10 r/s + 20 burst receive a 429 response.

Without nodelay, excess requests are queued, increasing latency.

API‑Key Based Limiting (multi‑tenant)

http {
    limit_req_zone $http_x_api_key zone=per_key:10m rate=100r/m;
    server {
        location /v1/ {
            if ($http_x_api_key = "") { return 401; }
            limit_req zone=per_key burst=20 nodelay;
            limit_req_status 429;
            proxy_pass http://backend;
        }
    }
}

Uses the request header X-API-Key as the limiting key.

Combined IP + API‑Key Limiting

http {
    limit_req_zone $binary_remote_addr zone=per_ip:10m rate=5r/s;
    limit_req_zone $http_x_api_key zone=per_key:10m rate=100r/m;
    server {
        location /v1/ {
            limit_req zone=per_ip burst=10 nodelay;
            limit_req zone=per_key burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

Both rules are AND‑combined; a request must satisfy both limits.

Advanced Scenarios

Fine‑grained delay Control (>= 1.15.7)

Using delay=N lets the first N excess requests pass immediately, while later excess requests are delayed.

location /api/ {
    limit_req zone=per_ip burst=12 delay=8;
}

Behavior table (converted to text):

≤10 requests: all processed normally.

11‑18 requests: first 10 normal, next up to 8 pass without delay.

19‑22 requests: first 10 normal, next 8 pass, remaining delayed.

>22 requests: first 10 normal, next 8 pass, then delayed, and finally rejected with 429.

Whitelist Bypass (geo + map)

http {
    geo $limit {
        default 1;
        127.0.0.1 0;
        10.0.0.0/8 0;
        172.16.0.0/12 0;
        192.168.0.0/16 0;
    }
    map $limit $limit_key {
        0 "";
        1 $binary_remote_addr;
    }
    limit_req_zone $limit_key zone=whitelist:10m rate=10r/s;
    server {
        location /api/ {
            limit_req zone=whitelist burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

If $limit_key is empty, no state is created and the request bypasses rate limiting.

Custom Error Responses

Replace the default 503 with a JSON 429 response using error_page or a static file.

http {
    limit_req_status 429;
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    server {
        error_page 429 = @rate_limit;
        location @rate_limit {
            internal;
            default_type application/json;
            add_header Retry-After 5;
            return 429 '{"code":429,"message":"Too many requests, please retry later","retry_after":5}';
        }
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

URI‑Based Differential Limiting

http {
    limit_req_zone $binary_remote_addr zone=login:10m rate=2r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
    limit_req_zone $binary_remote_addr zone=static:10m rate=100r/s;
    server {
        location /api/login/ { limit_req zone=login burst=5 nodelay; limit_req_status 429; proxy_pass http://backend; }
        location /api/ { limit_req zone=api burst=30 nodelay; limit_req_status 429; proxy_pass http://backend; }
        location /static/ { limit_req zone=static burst=200 nodelay; limit_req_status 429; root /var/www/static; }
    }
}

Dry‑Run Mode (>= 1.17.1)

http {
    limit_req_zone $binary_remote_addr zone=dry:10m rate=10r/s;
    server {
        location /api/ {
            limit_req_dry_run on;
            limit_req zone=dry burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

In dry‑run, Nginx logs which requests would have been limited without rejecting them. The variable $limit_req_status shows values such as PASSED, DELAYED, REJECTED, and their dry‑run equivalents.

Connection‑Count Limiting (limit_conn)

http {
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
    server {
        location /api/ {
            limit_conn conn_per_ip 10;      # max 10 concurrent connections per IP
            limit_conn_status 429;
            limit_req zone=api burst=20 nodelay;
            proxy_pass http://backend;
        }
    }
}

This is useful for limiting download connections or WebSocket sessions.

Configuration Validation and Performance Testing

Syntax Check

$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Load Testing with ab and wrk

# 150 requests, 20 concurrency
$ ab -n 150 -c 20 http://api.example.com/api/
Complete requests:      150
Failed requests:        30   # requests that received 429
Requests per second:    45.50 [#/sec] (mean)

# More precise test
$ wrk -t4 -c20 -d30s http://api.example.com/api/

Log Inspection

# Find limiting warnings
$ tail -f /var/log/nginx/error.log | grep "limiting requests"
2025/08/15 15:00:22 [warn] 1234: *57123 limiting requests, excess: 5.0 by zone "per_ip", client: 1.2.3.4, ...

# Extract 429 responses from access log
$ grep "429" /var/log/nginx/access.log | awk '{print $1,$4,$7,$9}' | tail -10

The excess field indicates how many requests exceeded the configured rate.

Memory Estimation for Zones

Each client state consumes ~128 bytes on a 64‑bit system.

1 MB ≈ 8 000 clients
10 MB ≈ 80 000 clients
100 MB ≈ 800 000 clients

Typical sizing:

10 MB for a service with ~100 k daily active users.

50‑100 MB for public high‑traffic services.

Zone size must account for the number of distinct keys (IP, API key, etc.).

If a zone is too small, entries are evicted, causing inaccurate limiting.

Limitations and Alternative Solutions

State is not shared across Nginx nodes → use Redis + Lua or a centralized rate‑limit gateway.

Statistics reset on restart → no built‑in persistence.

No sliding‑window “N requests per hour” → custom Lua scripts.

Key granularity limited to variables like IP, header, URI → OpenResty + Lua for richer keys.

Production Deployment Recommendations

Ensure monitoring captures 429 status codes before enabling limits.

Start with limit_req_dry_run for at least one business cycle.

Set thresholds with a safety margin (e.g., peak QPS + 20 %).

Derive limits from backend teams based on typical user interaction patterns.

Return friendly JSON messages for critical APIs instead of raw 429 pages.

Combine rate limiting with fallback mechanisms such as WAF, CDN, or DDoS protection.

Conclusion

The three essential steps for Nginx rate limiting are:

1. limit_req_zone (define zone & rate)
2. limit_req (apply to location)
3. limit_req_status (custom reject code)

Recommended configurations per scenario:

Login endpoint: rate=2r/s burst=5 nodelay General API: rate=20r/s burst=30 nodelay Multi‑tenant API: use API key as key with per‑key rate.

Internal services: whitelist IPs via geo + map.

Flash sales: temporarily lower rate and use error_page to show a queue page.

Best practices include using 429 instead of 503 for easy monitoring, preferring $binary_remote_addr over $remote_addr for memory efficiency, employing whitelist bypass, validating policies in dry‑run mode, and coordinating limits with business owners.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

traffic controlNGINXrate limitingAPI Securityleaky bucketlimit_connlimit_req
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.