Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild
Back to Blog Troubleshooting

Too Many Requests Error: Reading a 429 and Backing Off Properly

Sean

Platform Writer

Sep 01, 2026
8 min read

A 429 means you have sent more requests than the server is willing to accept in a window, and the correct response is almost always in the headers: a Retry-After value telling you exactly how long to wait.

Too Many Requests Error: Reading a 429 and Backing Off Properly

The mistake that turns a mild 429 into a genuine problem is retrying immediately. A client that hits a rate limit and retries in a tight loop generates far more requests than the one that triggered the limit, and many rate limiters extend the window on continued violation. The retry becomes the incident.

This covers reading the headers properly, implementing backoff that actually converges, and what to do on the server side when you are the one imposing the limit.

Table of contents

Read the headers before doing anything

A well-implemented 429 tells you precisely what to do, and clients routinely ignore it.

HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 1000
RateLimit-Remaining: 0
RateLimit-Reset: 30

Retry-After is the authoritative one. It is either a number of seconds or an HTTP date, and it is the server telling you when it will accept traffic again. Honouring it is not optional politeness; ignoring it is how a temporary limit becomes a longer ban.

The RateLimit-* family, where present, lets you avoid the error entirely by tracking your remaining budget and slowing down before you exhaust it. Some APIs use X-RateLimit-* instead, and some use both. Read whichever your API sends.

curl -sI https://api.example.com/v1/thing | grep -i -E 'retry-after|ratelimit'

A 429 with no Retry-After at all is a poorly implemented one, and there your only option is a backoff you choose yourself, starting conservatively.

Backoff that converges instead of thundering

The standard approach is exponential backoff with jitter, and the jitter is the part people leave out and then need.

Without jitter, every client that hit the limit at the same moment retries at the same moment, then again at the same moment, and you have built a synchronised traffic spike that keeps re-triggering the limit. Randomising the delay spreads them out.

import random, time
import requests

def request_with_backoff(url, headers=None, max_attempts=6):
    for attempt in range(max_attempts):
        response = requests.get(url, headers=headers, timeout=30)
        if response.status_code != 429:
            return response

        # The server's instruction always wins over our own calculation.
        retry_after = response.headers.get("Retry-After")
        if retry_after and retry_after.isdigit():
            delay = int(retry_after)
        else:
            # Full jitter: uniform between zero and the exponential ceiling.
            delay = random.uniform(0, min(60, 2 ** attempt))

        time.sleep(delay)

    raise RuntimeError("rate limited after %d attempts" % max_attempts)

Three properties this has that a naive retry does not. It respects the server’s instruction when one is given. It uses full jitter rather than a fixed delay, so concurrent clients desynchronise. And it gives up after a bounded number of attempts rather than retrying forever.

That last one matters more than it looks. An unbounded retry loop against a rate-limited endpoint is indistinguishable from an attack, and it will eventually be treated as one.

Not hitting the limit in the first place

Backoff is recovery. Avoiding the limit is better, and there are usually easy wins.

  • Batch. Many APIs offer an endpoint accepting multiple identifiers in one call. Replacing a hundred single-item requests with one batched request removes the problem entirely rather than pacing it.
  • Cache. A great deal of rate limit pressure is fetching data that has not changed. A short local cache on read-heavy endpoints often halves request volume for a few lines of code.
  • Use conditional requests. ETags and If-None-Match let the server answer 304 with no body, and many APIs do not count those against your limit or count them more cheaply.
  • Prefer webhooks to polling. If the API can call you when something changes, polling every thirty seconds to discover that nothing has is pure waste.
  • Track your budget. If the response tells you how many requests remain, slow down as you approach zero rather than sprinting into the wall.

The polling point is worth dwelling on because it is so common. A workflow polling frequently to detect a change that happens twice a day is spending thousands of requests to learn nothing, and it is usually the single biggest contributor to a rate limit problem.

Concurrency is usually the real cause

When a system that was fine starts hitting rate limits, the change is often not request volume but request concurrency.

Ten workers each making a request every second is ten requests per second, and if your limit is five you will hit it regardless of how politely each individual worker behaves. Each worker sees an occasional 429 and cannot understand why, because from its own perspective it is barely making requests at all.

The fix is a shared limiter rather than per-worker pacing. A token bucket in a shared store, consulted before each request, enforces a global rate across every worker in the fleet.

This is also where request-scaled deployment platforms surprise people. Scaling from two instances to fifty during a traffic spike multiplies your outbound request rate by twenty-five, and the third-party API you depend on starts refusing you at exactly the moment you most needed it. Capping maximum instances is a rate limit control as well as a cost control.

The same arithmetic applies to your database. Instances multiplied by pool size is the number of connections you are asking for, and the connection limits documentation covers where that ceiling sits.

If you are the one imposing the limit

Building rate limiting well is mostly about being informative rather than about the algorithm.

  • Always send Retry-After. A 429 without it forces every client to guess, and clients guess badly and usually too fast.
  • Send the RateLimit family too, so well-behaved clients can slow down before they hit the wall rather than after.
  • Limit per endpoint, not globally. Your expensive report generator and your cheap status endpoint should not share a budget.
  • Limit authentication attempts by account as well as by address. Distributed credential stuffing defeats address-based limits by construction.
  • Use a sliding window or token bucket rather than a fixed window. A fixed window permits double the intended rate across a boundary, which is a well-known and easily exploited property.
  • Return 429 rather than 403. They mean different things and clients handle them differently.

One more that is easy to forget: exempt your own health checks and internal traffic. A rate limiter that throttles your monitoring during an incident removes your visibility at the exact moment you need it.

How this fits the rest of the stack

Rate limits are a capacity conversation in disguise, on both sides of the connection, and the useful question is usually how much headroom the underlying plan actually has. The RunxBuild hosting calculator makes that concrete, and autoscaling between a floor and a ceiling plan handles the inbound half while a capped maximum keeps the outbound half from overwhelming whatever you depend on.

Useful related references:

FAQ

What does a 429 error mean?

You have sent more requests than the server accepts within its time window. It is a throttling response rather than a permanent refusal, and the response usually carries a Retry-After header telling you exactly how long to wait before trying again.

How long should I wait after a 429?

Exactly as long as the Retry-After header says, if one is present. If it is not, use exponential backoff with full jitter: a random delay between zero and a doubling ceiling, capped at around a minute, with a bounded number of attempts. Never retry immediately.

Why do I keep getting rate limited even though I slowed down?

Usually concurrency rather than per-client rate. Ten workers each making one request a second is ten requests a second in aggregate, even though each one feels slow. Enforce a shared limiter across the fleet rather than pacing each worker independently.

Does retrying make rate limiting worse?

Yes, frequently. Many rate limiters extend the window on continued violation, so a tight retry loop turns a thirty-second wait into a much longer one. Synchronised retries from multiple clients also re-trigger the limit immediately, which is why jitter matters.

What is the difference between 429 and 403?

A 429 is temporary and tells you to slow down; the same request will succeed later. A 403 is a refusal based on permission or policy and will not succeed on retry. APIs that return 403 for rate limiting make correct client behaviour impossible to implement.

#too many requests error#429#rate limiting#retry-after#exponential backoff