ByteScope

429

How to fix HTTP 429 Too Many Requests

4xx client errorIETF standardRFC 6585

A rate limiter has decided you are asking too often — a policy statement rather than a protocol one, which is why two services answering 429 may be counting entirely different things.

Why you are seeing 429, and what to do about it

The same three digits are three different problems depending on which side of the request you are on. Read the block that describes you.

Reproduce 429 with curl

Run this against the URL that failed. It prints the status without the error page, so you can see what the server said rather than what the browser rendered.

curl
curl -sS -o /dev/null -w '%{http_code} %header{retry-after}\n' 'https://example.com/api/health?n=[1-50]'

The square brackets are curl's URL globbing, so this is fifty sequential requests to one resource with a changing query string that defeats caching, and each prints one line. `%header{retry-after}` needs curl 7.84 or newer and prints that response header, or nothing when the server did not send one. The line you care about is the transition: the request number where 200 becomes 429 is the burst the limiter permits, and running the command again immediately tells you whether the window is fixed or sliding — an instant second 429 means the window has not rolled, while a fresh allowance means it has. An empty `retry-after` column is a finding in itself, because RFC 6585 makes that header a MAY, and its absence means every client is left to guess the wait. Watch for 503 in that column as well: nginx's `limit_req` answers 503 by default, so a run that switches to 503 at a consistent request number is still a rate limit. To find the sustained rate rather than the burst size, add `--rate 10/s` (also curl 7.84 or newer), which paces the series, and lower the number until the 429s stop.

What 429 actually means

429 is not in RFC 9110 at all: it comes from RFC 6585 §4, which defines it as the user having sent too many requests in a given amount of time, and says the response representation SHOULD explain the condition and MAY include a `Retry-After` header saying how long to wait. Both of those are deliberately soft, and the section that follows is softer still — RFC 6585 states outright that it does not define how the origin server identifies the user, nor how it counts requests. That single sentence is why this code behaves so differently from service to service: one API counts requests per token, another per IP address, another per endpoint or per account, and none of them is violating anything. It also means the number in front of you is a statement about a policy you cannot read from the response, so guessing the window from one 429 is guesswork. `Retry-After` itself is defined in RFC 9110 §10.2.3 and has two syntaxes a client must handle — a delay in seconds, or an HTTP-date — and since RFC 6585 makes it a MAY rather than a MUST, plenty of limiters send neither. There is no header for the remaining budget that everyone agrees on either; the de-facto set is `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, and GitHub, for example, documents its `x-ratelimit-reset` as a UTC epoch in seconds, so it needs converting before it means anything. Two more facts save time. 429 is not among the heuristically cacheable codes in RFC 9110 §15.1, so a cache will not hold one on its own. And the code is not the only way a limiter says no: nginx's `limit_req` module answers with `limit_req_status`, which defaults to 503, so a great deal of real rate limiting never appears as a 429 at all.

Codes 429 gets mistaken for

Confused withHow to tell them apart
503The most expensive mix-up in this pair is that nginx's own rate limiter answers 503 rather than 429: `limit_req_status` defaults to 503, so a load-related 503 with `limiting requests, excess: ... by zone ...` in the error log is a limit and not an outage. Both codes may carry `Retry-After`, so the header does not separate them — the log line and whether the failure tracks request volume do.
403A limiter is free to refuse with something other than 429. GitHub documents that exceeding its rate limits can return 403 or 429, and Cloudflare's rate limiting rules let the configured action be a block instead. So a 403 that appears only under load and clears after a wait is a limit rather than a permission problem, and auditing roles will find nothing.
401These two produce each other. A client that refreshes its token on every 401 turns one expired session into a burst against the auth endpoint, which is the endpoint most likely to be rate limited, and the resulting 429 then looks like an unrelated outage. If 429s are concentrated on your token endpoint, the bug is the refresh loop in the client, not the limit on the server.
Retry-AfterRFC 6585 makes this header a MAY on a 429, so its absence is legal and common, and RFC 9110 §10.2.3 gives it two forms: a delay in seconds or an HTTP-date. A client that parses only the numeric form silently treats a dated header as zero and retries immediately, which is the fastest way to convert one 429 into a longer block.
CloudflareWhen the limit is applied at the edge, the response is written by Cloudflare rather than by your origin, and your application logs will show nothing at all for those requests — which is exactly what makes it confusing. A branded page carrying a `cf-ray` value is the tell, and that ray id is what support can look up; the rate limiting rule's own configuration decides whether the visitor sees a 429 or a block.
Reason phrase
Too Many Requests
Class
4xx client error
Defined by
RFC 6585
Standard
IETF standard

Tools on this site

All of these run in your browser — nothing is uploaded.

Related status codes

Codes people usually end up reading in the same session as 429.

Frequently asked questions

How do I find out which server produced this status code?

Read the response headers rather than the page. curl -sS -o /dev/null -D - https://example.com/path prints them without the body, and server, via and cf-ray between them name the layer that answered. A cf-ray value means Cloudflare handled the response and is the id their support will ask for. To take the edge out of the picture entirely, repeat the request with --resolve example.com:443:203.0.113.10, which connects to the origin address you name while still sending the original host name and SNI — if the answer changes, the edge and the origin disagree.

Does the reason phrase after the number matter?

No, and you should never branch on it. RFC 9112 tells clients to ignore the reason phrase, servers are free to change it, and HTTP/2 and HTTP/3 do not carry one at all — so 404 Not Found over HTTP/1.1 arrives as a bare 404 over HTTP/2. The phrases on this site are the registered ones because they are what people search for and what appears in an HTTP/1.1 log, not because any software depends on them.

Should I retry a request that failed with this code?

Retry 5xx and 429; do not retry 4xx, because nothing about the request will be different next time. If the response carries Retry-After, honour it — RFC 9110 defines it for exactly this, and it may be either a number of seconds or a date. Otherwise use exponential backoff with jitter and a hard cap, and only for idempotent methods: a retried POST can charge a card twice. A retry storm against a server that is already failing is how a brief incident becomes a long one.

Can I make my server return a different status code?

Yes, and the only rule that matters is that the code has to be true. Anything that reads your responses automatically — search engines, monitoring, caches, client retry logic — makes decisions from the number and never from the page. Serving an error page with 200 hides the failure from your own alerting; serving 200 for a missing page gets the error indexed as content; returning 500 for a request that was simply malformed sends whoever is on call to the wrong half of the stack.

Still stuck on 429? browse every status code in the reference — or go back up to the block written for your side of the stack.