Fix 429 Rate Limit Errors on Nginx Reverse Proxy
Nginx returns 429 when API calls hit your rate limit. The culprit is almost always the limit_req directive. Here's how to fix it fast.
When You See This Error
You're running a web app or API behind Nginx, and suddenly clients get 429 Too Many Requests. This happens most often during a traffic spike — say you push a new feature and get 5000 requests per second against an API that normally handles 200. Or a bot goes haywire. The error is clear: Nginx is enforcing a rate limit you set.
Root Cause
Nginx uses the limit_req_zone directive to define a shared memory zone for rate tracking. Then limit_req in a location block checks incoming requests against that zone. When the rate exceeds your configured burst and delay, Nginx drops the request with 429. Simple, but easy to misconfigure.
The typical setup looks like this in nginx.conf http block:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;And in the location block:
limit_req zone=api burst=20 nodelay;The rate=10r/s means 10 requests per second. burst=20 allows up to 20 extra requests over the rate. nodelay means those burst requests aren't delayed — they're passed immediately until the burst is exhausted. After that, 429s happen.
So the root cause is either the rate is too low for your actual traffic, or the burst is too small. Or you forgot you set it.
Fix: Step by Step
- Find the limit_req_zone line. It's in the http block of your main nginx.conf. Usually at the top. If it's not there, check included files in /etc/nginx/conf.d/ or sites-enabled.
- Check the rate. Look at the
rate=value. Common examples:rate=30r/sfor 30 requests per second. If your API normally handles 100 req/s, you've found the issue. Increase it. Start withrate=100r/s. - Check the burst. In the location block where
limit_reqappears, increaseburst=. A good starting point is double your peak traffic per second. If you expect 200 req/s, setburst=400. - Test without nodelay first. Remove
nodelaytemporarily. This causes excess requests to be queued and delayed, not dropped. It's friendlier to clients. Only addnodelayif you must drop requests immediately. - Reload Nginx. Run
nginx -tto test config, thensystemctl reload nginxornginx -s reload. - Verify. Send a burst of requests using curl or a tool like
ab(Apache Bench).
If you see no 429s, you're done.for i in {1..50}; do curl -s -o /dev/null -w "%{http_code}\n" http://your-api.com/endpoint; done | sort | uniq -c
What If It Still Fails?
- You missed a location. Sometimes
limit_reqis in multiple location blocks. Check every server block and location in your config files. - Another service is rate-limiting. Your app, API gateway, or a CDN like Cloudflare can also return 429. Look at response headers. Nginx adds
X-RateLimit-LimitandX-RateLimit-Remainingheaders if configured. If you don't see those, the 429 might not be from Nginx. - Your rate limit is per IP.
$binary_remote_addrtracks by client IP. If traffic comes from a single IP (like a load balancer), all requests hit the same limit. Switch to$http_x_forwarded_foror a custom variable if you trust the header. - Logs tell the truth. Check Nginx error log:
tail -f /var/log/nginx/error.log. Look for lines like limiting requests, excess: 0.800 by zone "api". This tells you which zone is triggering the limit. - You're using limit_req in a wrong context. The
limit_req_zonemust be in the http block.limit_reqgoes in server or location. If you putlimit_req_zoneinside a server block, nginx will complain or ignore it.
One last thing: if you're on a shared hosting or a managed service (like AWS ALB), you might not control Nginx. In that case, check with your provider's docs. But if you manage the box, this fix works. I've used it on Ubuntu 20.04 with Nginx 1.18 and CentOS 7 with Nginx 1.20. Same deal.
Was this solution helpful?