Developers

429 Too Many Requests

A 429 is the politest error in HTTP: it does not say you are wrong, only that you are early. Handling it well is mostly about waiting the right amount of time, and not all retrying at once.

Rate limits protect servers from being overwhelmed, whether by an abusive scraper, a buggy loop, or a perfectly legitimate customer who suddenly runs a big batch job. The status code that says "you have hit the limit" is 429 Too Many Requests. Most 429 incidents are not caused by the limit itself but by what clients do next: retry immediately, retry in lockstep, or ignore the hint telling them when to come back.

What the spec says#

429 is not in the original HTTP/1.1 specs. It was added by RFC 6585 §4 in 2012, which says it "indicates that the user has sent too many requests in a given amount of time ('rate limiting')." The RFC adds three points worth knowing:

  • The response "SHOULD include details explaining the condition, and MAY include a Retry-After header indicating how long to wait."
  • The RFC "does not define how the origin server identifies the user, nor how it counts requests." Limits can be per IP, per API key, per account, per endpoint, or shared across a cluster.
  • "Responses with the 429 status code MUST NOT be stored by a cache."

Its security considerations also make a practical point: answering every request with a 429 still costs resources, so under real attack "it may be more appropriate to just drop connections." In other words, servers are not required to send 429, and a client that is hammering a server may see timeouts instead.

Retry-After#

RFC 9110 §10.2.3 defines Retry-After as either an HTTP date or a number of seconds:

HTTP/1.1 429 Too Many Requests
Retry-After: 120
Content-Type: application/problem+json

{"type": "about:blank", "title": "Too Many Requests", "status": 429,
 "detail": "Limit of 100 requests per minute exceeded for this API key."}
Retry-After: Fri, 31 Dec 1999 23:59:59 GMT

Your client must handle both forms. A surprising number of hand-written clients parse only the integer, crash on the date form, and then fall back to retrying immediately.

The RateLimit headers draft#

For years APIs have invented their own quota headers (X-RateLimit-Remaining and friends; GitHub, for example, uses x-ratelimit-remaining and sets it to 0 when you are limited). The IETF HTTP API working group is standardising this in draft-ietf-httpapi-ratelimit-headers, at version 11 as of May 2026 and still an Internet-Draft rather than an RFC. It defines two fields:

FieldParametersMeaning
RateLimit-Policyq quota, w window in seconds, optional qu unit and pk partition keyThe rules: for example 100 requests per 60 seconds
RateLimitr remaining quota, t seconds until the window resets, optional pkWhere you stand right now
HTTP/1.1 200 OK
RateLimit-Policy: "basic";q=100;w=60
RateLimit: "basic";r=20;t=56

Those values come from the draft's own examples: 20 requests remain in a 100-per-minute policy, with 56 seconds left in the window. The draft says that if a response carries both fields, "the Retry-After field MUST take precedence." Because it is still a draft, treat these headers as useful hints and keep your client working when they are absent.

Backoff with jitter#

When there is no Retry-After, the standard approach is exponential backoff: wait a little, then twice as long, then twice as long again, up to a cap. The refinement that matters is jitter, a random element in each delay. Without it, every client that failed at the same moment retries at the same moment, and the spike repeats. In his widely cited AWS post Exponential Backoff And Jitter, Marc Brooker simulated contending clients and found jittered backoff cut the total call count by more than half compared with plain exponential backoff, concluding that it should be "considered a standard approach for remote clients." His "full jitter" variant picks a random delay between zero and the current exponential ceiling.

JavaScript#

function retryAfterMs(res) {
  const h = res.headers.get("Retry-After");
  if (!h) return null;
  const secs = Number(h);
  if (Number.isFinite(secs)) return Math.max(0, secs * 1000);
  const when = Date.parse(h);               // HTTP-date form
  return Number.isNaN(when) ? null : Math.max(0, when - Date.now());
}

async function fetchWithBackoff(url, opts = {}, { tries = 5, base = 500, cap = 30000 } = {}) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, opts);
    if (res.status !== 429 && res.status !== 503) return res;
    if (attempt + 1 >= tries) return res;   // give up, let the caller decide
    const hinted = retryAfterMs(res);
    const ceiling = Math.min(cap, base * 2 ** attempt);
    const delay = hinted ?? Math.random() * ceiling;   // full jitter
    await new Promise(r => setTimeout(r, delay));
  }
}

Python#

import random, time
from email.utils import parsedate_to_datetime
from datetime import datetime, timezone
import requests

def retry_after_seconds(resp):
    h = resp.headers.get("Retry-After")
    if not h:
        return None
    if h.strip().isdigit():
        return int(h)
    try:
        when = parsedate_to_datetime(h)
        return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())
    except (TypeError, ValueError):
        return None

def get_with_backoff(url, tries=5, base=0.5, cap=30.0, **kw):
    for attempt in range(tries):
        resp = requests.get(url, timeout=10, **kw)
        if resp.status_code not in (429, 503) or attempt == tries - 1:
            return resp
        hinted = retry_after_seconds(resp)
        delay = hinted if hinted is not None else random.uniform(0, min(cap, base * 2 ** attempt))
        time.sleep(delay)

Wrong vs right#

while (res.status === 429) res = await fetch(url)

Honor Retry-After; otherwise back off exponentially with jitter, with a retry cap

Immediate retries extend the overload you just caused, and on many APIs they count against your quota too.

Server rate limit returns 503 Service Unavailable

Per-client limits return 429; reserve 503 for whole-service overload or maintenance

The nginx limit_req module defaults to limit_req_status 503;. Monitoring then pages someone because the service looks down, when really one client was too eager.

http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    server {
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            limit_req_status 429;
        }
    }
}

Using 403 to throttle search engine crawlers

Use 429 or 503 briefly, or reduce load another way

Google's docs say: "Don't use 401 and 403 status codes for limiting the crawl rate." Google treats 429 as a signal that the server is overloaded and slows down; but persistent server errors can eventually drop URLs from the index, so this is a short-term measure.

Common causes#

CauseWhat it looks likeFix
Loop or retry storm in your codeHundreds of identical requests per secondAdd backoff; cache results; fix the loop
Parallelism too highBatch job runs fine with 2 workers, 429s with 50Limit concurrency; use a token bucket on the client
Shared IP429 on the first request from an office, VPN or CI runnerAuthenticate so limits apply per key, not per IP
Wrong tier or planLimits far lower than expectedCheck the API's published limits for your plan
Login throttling429 after several failed passwordsWait; this limit is deliberate and protects your account
Polling instead of webhooksSteady background traffic eating the quotaSwitch to webhooks or conditional requests (ETags)

If you are a visitor#

  • Stop refreshing; each refresh can reset or extend the wait.
  • Wait a minute or two, then try once.
  • If you are on a shared network (VPN, office, mobile carrier), try another connection: you may be sharing a limit with many other people.
  • Close extensions or scripts that might be polling the site in the background.

Designing a limit people can live with#

The Handling Overload chapter of Google's SRE book argues for per-customer limits and for clients that throttle themselves before the server has to. In practice: publish your limits, send Retry-After on every 429, return the quota headers on successful responses too so clients can slow down before they hit the wall, and explain the limit in the body in plain language. A 429 that tells callers exactly what happened and when to come back is a 429 they will only see once.

Frequently asked questions

What does HTTP 429 mean?

RFC 6585 defines 429 Too Many Requests as the user having sent too many requests in a given amount of time, which is rate limiting. The response may include a Retry-After header saying how long to wait.

How long should I wait after a 429?

If the response has a Retry-After header, wait at least that long; it can be a number of seconds or an HTTP date. If it does not, back off exponentially with random jitter, starting small and capping the maximum delay.

Is 429 a client error or a server problem?

It is a 4xx code, so formally the client is expected to change its behaviour (slow down). Google's crawlers, however, treat 429 like a server error, as a signal that the server is overloaded, and slow their crawling.

Should rate limits return 429 or 503?

429 when a specific caller has exceeded its allowance; 503 when the whole service is overloaded or in maintenance. nginx's limit_req returns 503 by default, which you can change with limit_req_status 429.

What are the RateLimit and RateLimit-Policy headers?

They come from an IETF HTTP API working group Internet-Draft (version 11, May 2026) that standardises how servers advertise quotas: RateLimit-Policy describes the policy, RateLimit reports the remaining quota and time window. It is not yet an RFC.

Sources

Every factual claim on this page links to one of these. Checked October 2026. See how we source.

  1. RFC 6585 §4: 429 Too Many Requests rfc-editor.org
  2. RFC 6585 §7.2: Security considerations for 429 rfc-editor.org
  3. RFC 9110 §10.2.3: Retry-After rfc-editor.org
  4. IETF draft-ietf-httpapi-ratelimit-headers datatracker.ietf.org
  5. Marc Brooker, Exponential Backoff And Jitter (AWS Architecture Blog) aws.amazon.com
  6. nginx: ngx_http_limit_req_module nginx.org
  7. Google Search Central: How HTTP status codes affect Google Search developers.google.com
  8. GitHub Docs: Troubleshooting the REST API (rate limits) docs.github.com
  9. MDN: 429 Too Many Requests developer.mozilla.org
  10. Google SRE Book: Handling Overload sre.google