429 Too Many Requests through a proxy: find the limit
A 429 is a rate limit: something counted your requests and asked you to slow down, often with a Retry-After header that says for how long. Through a proxy it is almost always the website, after the tunnel opened. Find what the limit counts (the IP, a cookie, an API key or an account), then send fewer requests, not the same requests from more addresses.
Reviewed by John Hale. Last updated .
What it looks like
> GET /search?q=proxy HTTP/2
< HTTP/2 429
< retry-after: 30
< content-type: text/htmlrequests.exceptions.HTTPError: 429 Client Error: Too Many Requests for url: https://example.com/search?q=proxyWhat does 429 Too Many Requests mean?
RFC 6585 defines 429 as the user having sent too many requests in a given amount of time. The server may add Retry-After, either as seconds (Retry-After: 30) or as a date. It does not have to say how it counts.
Limits count different things. Some count requests per IP address, some per session cookie or API key, and some per account across every address. Others limit concurrency, so 20 parallel requests trip a limit that 20 requests one after another do not.
A proxy provider can enforce its own rate or connection limits as well. Those usually arrive as the answer to CONNECT or as the provider's own error, so curl -v tells the two apart.
What causes a 429 through a proxy?
Most likely first.
- Too many requests per second to one site. Scraping and monitoring jobs usually start at full speed. A burst at start-up, or parallel workers fetching the same pages, is the most common trigger.
- Retries without backoff. A client that retries every failure at once raises its rate exactly when the site is asking it to slow down, and can turn a 30-second limit into a long one.
- One sticky IP carries all the traffic. A sticky session is the right choice for logins and carts, but it puts every request from that worker on one address.
- The limit follows an account, cookie or API key. If every worker sends the same session cookie, token or API key, the site counts them together whatever address each one uses.
- Other traffic on the same address. Residential and mobile exits are shared networks: a household or carrier address can carry other traffic to the same site, which counts against a per-IP limit.
How do you find which limit you hit?
Read the headers that came with the 429
Retry-Aftergives the wait.RateLimitorX-RateLimitheaders, where a site sends them, show the quota left and the window.Rate-limit headerscurl -s -D - -o /dev/null -x http://HOST:PORT -U "USER:PASS" https://TARGET/ \ | grep -iE "^(HTTP|retry-after|x-ratelimit|ratelimit)"Confirm who sent it
A 429 after
200 Connection establishedis the site's. A 429 instead of the tunnel is the proxy's.Proxy's answer, then the site'scurl -v -x http://HOST:PORT -U "USER:PASS" https://TARGET/ -o /dev/null 2>&1 \ | grep -iE "^(> CONNECT|< HTTP|< x-proxy-reason|\* CONNECT)"Measure the rate that triggers it
Run a slow loop and note when the first 429 appears. That tells you more about the limit than any guess.
One request every 2 secondsfor i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \ -x http://HOST:PORT -U "USER:PASS" https://TARGET/ sleep 2 doneFind what the limit follows
Send the same request from two sticky sessions without cookies, then with the same cookie. If the second exit is limited only when it shares the cookie or token, the limit follows the session, not the IP.
How do you fix a 429?
Honor Retry-After and back off
Wait at least the
Retry-Aftertime before the next request to that site. Without one, start at a second or two and double the wait after each further 429, with random jitter and a cap on retries.Python requestsimport random, time import requests proxy = "http://USER:PASS@HOST:PORT" session = requests.Session() session.proxies = {"http": proxy, "https": proxy} def get(url, tries=5): for attempt in range(tries): r = session.get(url, timeout=30) if r.status_code != 429: return r wait = r.headers.get("Retry-After", "") delay = int(wait) if wait.isdigit() else 2 ** attempt time.sleep(delay + random.uniform(0, 1)) return rThe same in Node.js
Release the body of each refused response before waiting, so the connection goes back to the pool.
Node.js with undiciimport { fetch, ProxyAgent } from "undici"; const dispatcher = new ProxyAgent("http://USER:PASS@HOST:PORT"); async function get(url, tries = 5) { let res; for (let attempt = 0; attempt < tries; attempt++) { res = await fetch(url, { dispatcher }); if (res.status !== 429) return res; await res.body?.cancel(); const seconds = Number(res.headers.get("retry-after")); const delay = seconds > 0 ? seconds * 1000 : 2 ** attempt * 1000; await new Promise((resolve) => setTimeout(resolve, delay + Math.random() * 1000)); } return res; }Send less
Lower concurrency per site, start workers gradually instead of all at once, cache pages you already have, and use the site's API where it offers one: API limits are documented. Spreading one job across more addresses does nothing for limits on cookies, keys or accounts, and working around a limit the site has stated can breach its terms.
If you use TrueProxies
The gateway never answers 429. When it refuses a connection itself, it answers 407 with the reason in X-Proxy-Reason, so curl -v tells its refusals apart from a website's 429. Reusing connections with keep-alive or a connection pool avoids opening more connections than a job needs.
So a 429 inside a tunnel is the website's. Rotating sessions request a new IP for each connection, and the same IP can appear again. That spreads a per-IP count but does nothing for limits on cookies, keys or accounts.
If nearly every connection from a service fails at its destination for two minutes, the gateway pauses new connections briefly and says so in X-Proxy-Reason, with the counts and the wait. Slow the client down; the pause ends on its own.
Test the proxy from outside your network
The free proxy checker connects from our servers and reports the failed stage as a code, so you can tell a proxy problem from one on your computer or network.
Questions about 429 Too Many Requests through a proxy
Does a 429 mean my proxy is bad?
No. It means a limit was reached. Through an open tunnel the limit is the website's, whichever proxy you use.
How long should I wait after a 429?
The Retry-After value, when there is one. Without it, start at a second or two and double the wait after each further 429, up to a cap.
Why do I get a 429 on the very first request?
The exit's address may already be over the site's per-IP limit from other traffic, or the site limits whole networks. Try another exit and a slower start.
Can the proxy send a 429 instead of the website?
Some providers use 429 for their own plan limits, as the answer to CONNECT. curl -v shows it before any 200 Connection established line.
Related errors
403 Forbidden through a proxy
Usually the website's refusal after the tunnel opened; sometimes the proxy refusing the destination.
ECONNRESET through a proxy
Connection reset by peer in Node.js or Python: stale keep-alive sockets, the wrong port, or a cut route.
502 Bad Gateway from a proxy
The proxy, or the site's own gateway, got no usable answer from the server behind it.
The proxy checker error codes page maps each checker code to the stage that failed, and proxy errors explained covers timeouts and slow connections.