A 429 response means the server is rate limiting you: this client sent more requests in a time window than it allows. Stop sending for a while. If the reply carries a Retry-After header, wait that long; if not, retry with exponential backoff plus random jitter so many clients do not retry in lockstep.
| Part | What it says | What your client should do |
|---|---|---|
| Status 429 | Too many requests in a given amount of time | Pause this client, do not hammer |
| Retry-After: 120 | Wait 120 seconds (delay in seconds form) | Sleep at least that long |
| Retry-After: Wed, 21 Oct 2026 07:28:00 GMT | Wait until that time (HTTP-date form) | Subtract the current time, then sleep |
| No Retry-After | The server gave no hint | Back off exponentially with jitter |
| Caching | A 429 must not be stored by a cache | Never serve a saved 429 later |
Updated
Set the first delay, the ceiling and how many retries you allow, then choose a jitter style. The calculator lists the wait range for every attempt and the worst-case total, so you can see whether your client gives up before the server's window resets.
Base delay (ms)
Cap per wait (ms)
Retries
Jitter style
| Retry | Shortest wait | Longest wait |
|---|---|---|
| 1 | 0 s | 0.5 s |
| 2 | 0 s | 1 s |
| 3 | 0 s | 2 s |
| 4 | 0 s | 4 s |
| 5 | 0 s | 8 s |
| 6 | 0 s | 16 s |
Worst case before giving up: 31.5 s
Reads both forms of Retry-After, falls back to full jitter, and stops after a fixed number of retries.
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function retryAfterMs(header) {
if (!header) return null;
const secs = Number(header);
if (Number.isFinite(secs)) return Math.max(0, secs * 1000);
const when = Date.parse(header);
return Number.isNaN(when) ? null : Math.max(0, when - Date.now());
}
async function fetchWithRetry(url, options = {}, { base = 500, cap = 30000, retries = 6 } = {}) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429 || attempt >= retries) return res;
const hinted = retryAfterMs(res.headers.get("Retry-After"));
const ceiling = Math.min(cap, base * 2 ** attempt);
await sleep(hinted ?? Math.random() * ceiling);
}
}limit_req rejects with 503 by default; limit_req_status switches it to 429.
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
server {
location /api/ {
limit_req zone=api burst=10 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:3000;
}
}
}A short body explaining the limit, and a Retry-After in seconds.
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
{"error": "rate_limited", "message": "Limit is 5 requests per second. Try again in 30 seconds."}
APIs, login forms and search endpoints count requests per user, per key or per IP address and refuse the extra ones. The specification leaves the counting up to the server, so one API may limit per minute and another per day.
The response should explain the limit in its body, and may add Retry-After to say how long to wait.
Plain exponential backoff doubles the wait each time up to the cap: min(cap, base * 2^attempt). Full jitter picks a random wait between zero and that number. Equal jitter keeps half of it and randomises the other half, which guarantees some minimum pause.
Full jitter spreads load best, which is why it is a sensible default. Equal jitter suits clients that must never retry instantly.
It is in the 4xx class, so it describes the client's behaviour: too many requests. Nothing is broken on the server. The fix is to send fewer requests or spread them out.
Either a number of seconds to wait, such as 120, or an HTTP date after which you may try again. Your client should handle both forms.
Without jitter every client that failed at the same moment retries at the same moment again, which recreates the spike. A random wait inside each window spreads the retries out.
The limit_req module rejects with 503 unless you change it. Add limit_req_status 429; next to your limit_req line so clients see the more accurate code.
It must not. RFC 6585 says responses with the 429 status code must not be stored by a cache, so each client gets a fresh answer once the limit lifts.
Related: HTTP status codes reference, JavaScript fetch with async and await, HTTP 304 Not Modified, Nginx 502 and other nginx errors, Webhook vs API.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.