DEVESSENTIALS

HTTP 429 Too Many Requests

429 means rate limiting. It isn't defined in RFC 9110 but in RFC 6585 (2012). The server may include a Retry-After header saying how long to wait, and should explain the limit in the body.

4xx · Client errorRFC 6585 §4

429 Too Many Requests

The client has sent too many requests in a given amount of time.

Who fixes it: Client. The client should slow down and retry later. If legitimate traffic is being limited, the server's limits need adjusting.

What 429 means

RFC 6585 doesn't say how the server identifies the client (IP, API key, user, account) or what it counts (requests, tokens, concurrent connections). The same 429 can mean "this IP is too noisy" or "your plan's monthly quota is used up".

RFC 6585 says responses with a 429 status must not be stored by a cache.

Source: RFC 6585 §4.

Common causes

  • A retry storm: code that retries immediately on failure, multiplying traffic exactly when the server is struggling.
  • Unbounded parallelism: Promise.all() over 1,000 items fires 1,000 requests at once.
  • Many clients sharing one IP (office NAT, CI runners, serverless egress) against an IP-based limit.
  • Per-minute or per-day quotas on third-party APIs such as OpenAI, Stripe or Google APIs, often reported in RateLimit or X-RateLimit-* headers.
  • Stricter limits on login and password-reset endpoints to slow down brute-force attempts.
  • Crawlers, scrapers or your own monitoring hitting a site faster than its limit.

How to handle and send a 429

Client: honour Retry-After, otherwise back off with jitter

Retry-After is either a number of seconds or an HTTP date. Without it, use exponential backoff with random jitter so many clients don't retry in lockstep, and cap the retries.

async function fetchWithRetry(url, options = {}, maxRetries = 5) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, options);
    if (res.status !== 429 || attempt === maxRetries) return res;

    const header = res.headers.get('Retry-After');
    let delay;
    if (header && !Number.isNaN(Number(header))) {
      delay = Number(header) * 1000;                           // seconds
    } else if (header) {
      delay = Math.max(0, Date.parse(header) - Date.now()) || 1000; // HTTP date
    } else {
      delay = Math.random() * Math.min(30_000, 500 * 2 ** attempt); // full jitter
    }
    await new Promise((resolve) => setTimeout(resolve, delay));
  }
}

nginx: limit_req returns 503 unless you change it

limit_req_status defaults to 503, which tells clients and crawlers the server is down. Set it to 429.

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;   # default is 503
            proxy_pass http://127.0.0.1:3000;
        }
    }
}

Express: express-rate-limit

It responds 429 with Retry-After and the standard RateLimit headers. Behind a proxy, set trust proxy or every client shares the proxy's IP.

import { rateLimit } from 'express-rate-limit';

app.set('trust proxy', 1);
app.use('/api/', rateLimit({
  windowMs: 60 * 1000,        // 1 minute
  limit: 100,                 // requests per IP per window
  standardHeaders: 'draft-7', // RateLimit + RateLimit-Policy headers
  legacyHeaders: false,
}));

Limit concurrency instead of firing everything at once

Process work in small batches or with a concurrency limiter such as p-limit, so you stay under the limit in the first place.

Headers that matter with 429

Retry-After
Retry-After: 120 (seconds) or Retry-After: Fri, 16 Oct 2026 10:00:00 GMT. Clients should wait at least that long.
RateLimit / RateLimit-Policy
IETF draft headers with the limit, remaining quota and reset time. Many APIs still use the older X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.

429 vs similar status codes

  • 503 Service Unavailable: The server as a whole is overloaded or down. 429 is about one client's usage.
  • 403 Forbidden: Access denied regardless of rate. Some APIs misuse 403 for rate limits; prefer 429.

SEO impact of 429

Google treats 429 like a server error: Googlebot slows its crawl rate, and if 429s persist, URLs already indexed can be dropped. Don't rate-limit Googlebot unless your server truly can't cope, and if you have to, 429 or 503 is the right signal, not 403 or 404. Verify Googlebot by reverse DNS, not by user agent.

Related status codes

Looking for another code? Search the full HTTP status codes list, or read which code your API should return in the REST API status codes guide.

Learn more

Browse all developer guides →

Frequently Asked Questions

How long should I wait after a 429?▾

If the response has a Retry-After header, wait that long; it's either a number of seconds or an HTTP date. Without it, back off exponentially (for example 0.5s, 1s, 2s, 4s) with random jitter, and cap the number of retries.

Is 429 a client or server error?▾

It's a 4xx, so the server attributes it to the client sending too much. The fix can be on either side: the client should slow down and retry later, and the server owner may need to raise limits that are too tight for legitimate traffic.

Why does nginx return 503 instead of 429 when rate limiting?▾

limit_req_status and limit_conn_status default to 503. Add limit_req_status 429; next to your limit_req directive so clients and crawlers can tell rate limiting apart from a real outage.