← News
/PLAYBOOK · 2026-07-25

Nginx limit_req in practice: complete CC-attack defense config

Complete Nginx limit_req CC-attack defense: rate-limit policy, leaky-bucket internals, whitelist, and load-test verification.

Scope

When a web server is hit by a CC (Challenge Collapsar) attack, the attacker floods it with high-frequency HTTP requests, exhausting the connection pool and bandwidth so legitimate users cannot connect. This article shows how to build a production-ready rate-limit defense with Nginx's native `limit_req` module. Applies to standalone Nginx or Nginx as a reverse proxy.

Prerequisites

Run this to confirm the module is present:

nginx -V 2>&1 | grep -o 'http_limit_req_module'

Output `http_limit_req_module` means the module is enabled.

Internals

The `limit_req` module rate-limits with the leaky-bucket algorithm. Core mechanism:

When the rate exceeds the threshold, Nginx returns 503 Service Unavailable and drops malicious requests immediately, protecting the backend.

Steps

Step 1: Define the rate-limit zone

Edit `/etc/nginx/nginx.conf`. Add the zone inside the `http` block:

http {
    # ... other configuration ...

    # Limit each IP to at most 10 requests per second; 10MB buffer
    limit_req_zone $binary_remote_addr zone=cc_defense:10m rate=10r/s;

    # Optional: rate-limit by request URI (for specific endpoints)
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;
}

Parameters:

Step 2: Apply the rule in server/location

Edit the site config (e.g. `/etc/nginx/conf.d/site.conf`):

server {
    listen 443 ssl http2;
    server_name helixcyber.ai;

    # SSL certificate configuration omitted

    # Global rate limit (all requests)
    location / {
        limit_req zone=cc_defense burst=20 nodelay;
        limit_req_status 503;

        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Rate-limit API endpoints separately (stricter)
    location /wp-json/ {
        limit_req zone=api_limit burst=10 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Do not rate-limit static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        # Do not set limit_req
        proxy_pass http://127.0.0.1:8080;
    }
}

Key parameters:

Step 3: IP whitelist (trusted sources skip the limit)

Use `geo` to define a whitelist so CDN origin IPs and monitoring probes are not limited:

http {
    # Define trusted IP list
    geo $limit_ip {
        default 1;          # Rate-limit by default
        127.0.0.1 0;        # Do not rate-limit localhost
        10.0.0.0/8 0;       # Do not rate-limit internal network
        # CDNoriginIP
        123.45.67.0/24 0;
    }

    # Control the rate-limit key with a variable: trusted IPs use an empty value to skip the limit
    map $limit_ip $limit_key {
        0 "";
        1 $binary_remote_addr;
    }

    limit_req_zone $limit_key zone=cc_defense:10m rate=10r/s;
}

Whitelisted IPs then skip the rate-limit rule.

Step 4: Custom error page

Serve a friendlier 503 page:

server {
    # ... other configuration ...

    error_page 503 = @cc_blocked;

    location @cc_blocked {
        default_type application/json;
        return 503 '{"code":503,"msg":"Too many requests, please try again later"}';
    }
}

Step 5: Test and reload

# syntax check
nginx -t

# graceful reload
nginx -s reload

Verification

Simulate high-frequency requests with `ab` to confirm the limit:

# Send 100 requests, concurrency 20
ab -n 100 -c 20 https://helixcyber.ai/

# Expected:
# Complete requests:      100
# Failed requests:        80   ← requests exceeding the rate are rejected
# Non-2xx responses:      80   ← return 503 status code

Low-frequency legitimate requests should all return 200:

# 1 request per second, 10 total
for i in $(seq 1 10); do
    curl -s -o /dev/null -w "%{http_code}\n" https://helixcyber.ai/
    sleep 1
done
# Expected: 10 lines of 200

Check the Nginx error log for limit events:

tail -f /var/log/nginx/error.log | grep "limiting requests"

# Expected example:
# limiting requests, excess: 20.000 by zone "cc_defense", client: 1.2.3.4 ...

FAQ

Q1: How to choose burst and nodelay?

`burst` sizes the burst queue. Without `nodelay`, burst requests are processed at the configured rate (they wait in queue). With `nodelay`, burst requests are processed immediately but still count against the quota. For CC defense, use `nodelay` so legitimate users do not wait in queue.

Q2: Site sits behind a CDN; every request IP is the CDN origin. How to rate-limit?

Extract the real client IP from `X-Forwarded-For` or `X-Real-IP`. Add this in the `http` block:

set_real_ip_from 123.45.67.0/24;  # CDN origin IP range
real_ip_header X-Forwarded-For;
real_ip_recursive on;

`$binary_remote_addr` is then replaced with the real client IP, and the limit applies per real IP.

Q3: Rate limit is too strict and blocks legitimate users. What then?

Tune the policy: raise `rate` and `burst`; skip limits on static paths; return 429 instead of 503 (better HTTP semantics); combine with `limit_conn` (concurrent connection limit) instead of relying on request rate alone.

Takeaway

Key points:

Further reading: `limit_conn` concurrent-connection limits, Nginx Lua dynamic rate limiting, and Fail2ban auto-ban of malicious IPs for a fuller CC-defense stack.