429 means a limit was hit. The useful question is whose limit: your own web server, a proxy or CDN in front, an API you are calling, or the application itself. Each is a different fix, and the response headers usually say which.
Read the response first
curl -sSI https://yourdomain.com/api/thing | grep -i -E 'retry-after|ratelimit|x-'
Retry-After tells you how long to wait. RateLimit headers tell you the ceiling and what is left. A 429 with no such headers is usually your own server or a proxy that was not configured to explain itself.
If it is your own Nginx
grep "limiting requests" /var/log/nginx/error.log | tail
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
}
Behind a CDN or a load balancer, $binary_remote_addr is the PROXY, so every visitor shares one bucket and a handful of them trips the limit for everybody. Set up the real client address first - see Cloudflare and the real visitor IP.
If you are the one being limited
- Respect Retry-After. Retrying immediately extends most bans.
- Back off exponentially, with jitter, so a fleet of workers does not retry in lockstep.
- Cache what you fetch. Most rate limits are hit by asking the same question repeatedly.
- Batch where the API supports it - one call for fifty records rather than fifty calls.
for i in 1 2 3 4 5; do
curl -fsS "$URL" && break
sleep $(( (2 ** i) + RANDOM % 3 ))
done
If it is a login endpoint
Then it is working as intended. See rate limiting a login endpoint.