To stop brute-force and scraping, cap how many requests a client can make. Define a shared zone at http { }, then apply it where it matters.
At http { }:
# 10 requests/second per IP; 10m of state holds ~160k IPs.
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# Return 429 (Too Many Requests) instead of the default 503.
limit_req_status 429;
In a location (e.g. an API or login):
location /api/ {
limit_req zone=req_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location = /login {
limit_req zone=req_limit burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
}
How it works:
rate=10r/sis a steady allowance using a leaky-bucket.5r/mis also valid for very slow limits.burst=20lets short spikes through — up to 20 queued over the limit.nodelayserves those burst requests immediately (rather than spacing them out); without it bursts are throttled to the steady rate.- Once the burst is exhausted, extra requests get 429.
$binary_remote_addrkeys by client IP compactly. Behind a CDN/load balancer the real IP is inX-Forwarded-For— set upreal_ipfirst (below) or you’ll rate-limit the proxy’s IP, throttling everyone at once.
Behind a trusted proxy, recover the real client IP first:
# at http or server level
set_real_ip_from 10.0.0.0/8; # your load balancer's range
real_ip_header X-Forwarded-For;
real_ip_recursive on;
Tune burst so normal users (a page that fires several XHRs at once) aren’t hit; logs show limiting requests when it triggers.
sudo nginx -t && sudo nginx -s reload