429 Too Many Requests means a rate limit was hit: the server counted too many requests from you in a short time and is asking you to slow down. It protects sites and APIs from overload and abuse. If you run the site, the question is whether the limit is stopping attackers or blocking real people.
If you are visiting the site or using an API
Wait, then try again. Many 429 responses include a Retry-After header saying how many seconds to wait. Reloading repeatedly makes it worse, because every reload counts as another request.
If you see it often:
- Behind a shared connection (an office, a school, a mobile carrier or a VPN), many people share one IP address, and their requests count together. Switching off the VPN often helps.
- Using an API, check its documentation for the limit and slow your code down. Read the
Retry-Afterheader and wait that long, and back off exponentially (wait 1, 2, 4, 8 seconds) on repeated 429s rather than retrying at once. - Browser extensions that prefetch links or check pages in the background can hit limits on your behalf.
If you run the site
1. Find where the limit is set
A 429 can come from several layers. Check each:
| Layer | Where to look |
|---|---|
| Nginx | limit_req and limit_conn in the site configuration |
| Your app or CMS | Login protection, API throttling or security plugins |
| CDN or proxy | Rate limiting rules in the CDN dashboard |
| Hosting platform | Some hosts rate-limit accounts or specific paths such as wp-login.php |
| An API you call | Your server is the one being limited, and passes the 429 on |
Search the server first:
grep -Rn "limit_req\|limit_conn" /etc/nginx/
sudo grep "limiting requests" /var/log/nginx/error.log | tail
A log line like limiting requests, excess: 10.520 by zone "perip", client: 203.0.113.7 names the zone and the visitor. If nothing appears in Nginx’s log, the limit is further in, in your app, or further out, at the CDN.
2. How Nginx rate limits work
A typical setup has two parts. A zone, defined once in the http block, sets the rate:
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
limit_req_status 429;
Then a limit_req line applies it to a location:
location / {
limit_req zone=perip burst=20 nodelay;
}
rate=5r/s allows 5 requests a second per IP address on average. burst=20 lets a visitor briefly go over that, which matters because one page view loads many files at once. Without a burst, a normal page load can trip the limit. nodelay serves burst requests immediately instead of spacing them out.
Without limit_req_status 429;, Nginx returns 503, which looks like an outage. If real visitors are being limited, raise the burst first, then the rate. Apply strict limits only where they are needed, such as login, search and contact forms, and leave static files unlimited:
location = /wp-login.php {
limit_req zone=perip burst=5;
# ...the usual PHP handling
}
Test and reload: sudo nginx -t && sudo systemctl reload nginx.
3. Behind a CDN, limit the real visitor IP
Behind Cloudflare or another proxy, every request reaches your server from the proxy’s addresses. If Nginx limits by $binary_remote_addr, thousands of visitors share a handful of IPs and hit the limit together. Tell Nginx to trust the proxy and use the visitor’s real address:
set_real_ip_from 173.245.48.0/20;
# ...one line for each of the CDN's published ranges
real_ip_header CF-Connecting-IP;
Cloudflare publishes its ranges at cloudflare.com/ips. Other CDNs use X-Forwarded-For and publish their own lists. Only trust addresses that really belong to the proxy, or anyone can fake their IP with a header.
4. Is it an attack or real traffic?
Count requests per IP address and per URL in the access log:
sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
sudo awk '$9 == 429 {print $1, $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
The second command lists who is being limited and on which page. A handful of addresses hammering wp-login.php or xmlrpc.php is a password-guessing attack, and the limit is doing its job; block those addresses at the firewall or with fail2ban. Many different addresses limited on normal pages means the limit is too tight for real visitors.
Look up an address before blocking it. Search engine crawlers can trip limits, and blocking Googlebot removes you from search. Google’s crawlers can be confirmed with a reverse lookup: host 66.249.66.1 should return a name ending in googlebot.com.
5. When your server is the one being limited
If your site calls an outside API, such as a payment provider, a mapping service or an AI model, and that API returns 429, your page fails too. Cache the API’s answers so you ask less often, spread out background jobs, and respect the Retry-After value. A sudden 429 from an API you have used for months usually means a bug is calling it in a loop.
Confirm it is fixed
Send a quick burst of requests and count the answers:
for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code}\n" https://example.com/; done | sort | uniq -c
All 200 means normal browsing is not limited. Run it against the login page too, where you do want some 429 answers after the first few.
Related
- 503 Service Unavailable, what Nginx returns for rate limits unless told otherwise.
- What a CDN is.
- What a reverse proxy is.
Something out of date? Software changes. If a step no longer works, tell us and we will check it and update the page.



