Proxy Error Codes Explained (407, 502, 429, and More)
Proxy error codes like 407, 403, 429, 502, and 504 each point to one specific fix. Learn what each means, how to diagnose it, and how to retry safely.

When a scraper fails, the status code is your fastest diagnostic. Proxy error codes tell you exactly where the request broke: at the proxy hop, at the target server, or somewhere on the wire in between. Most of them map to one specific fix, and retrying blindly usually makes things worse.
This guide decodes the codes you'll actually hit (407, 403, 429, 502, 503, 504), covers the connection failures that aren't HTTP codes at all, and shows you how to tell them apart and retry the right way. Every example uses real error strings and working code.
Key takeaways
- A status code has an origin. The same 429 can come from your proxy provider (concurrency limit) or the target site (rate limit), and the fix is different for each.
407,403from a plan limit, and422are configuration errors. Retrying them repeats the same failure. Fix the request instead.429and5xxare transient. Retry those with exponential backoff and jitter, and honorRetry-Afterwhen the server sends it.- Connection refused, timed out, and reset are transport-layer failures, not HTTP responses. Which one you get tells you whether the problem is the port, a firewall, or a dropped session.
Quick Reference: Proxy Error Codes
Here are the HTTP status codes you'll see most often when a request goes through a proxy. The column that trips people up is "Where it comes from." A proxy can generate the status itself, or it can forward a status the target site produced. Read that column first.
| Code | Name | Where it comes from | Common cause | Fix |
|---|---|---|---|---|
| 407 | Proxy Authentication Required | The proxy | Missing or wrong credentials, or your IP is not whitelisted | Send correct `Proxy-Authorization`, or whitelist your current IP |
| 403 | Forbidden | Proxy or target | Plan doesn't allow that IP type or region (proxy), or the site blocked you (target) | Check plan permissions; if it's the target, rotate IP and fix fingerprint |
| 429 | Too Many Requests | Proxy or target | You crossed a rate or concurrency limit | Slow down, honor `Retry-After`, add backoff, spread load across IPs |
| 502 | Bad Gateway | The proxy | Proxy got an invalid or empty response from the upstream/exit node | Retry on a new IP; the exit node likely failed |
| 503 | Service Unavailable | Proxy or target | Proxy pool overloaded or in maintenance, or the target is shedding load | Back off, reduce concurrency, retry |
| 504 | Gateway Timeout | The proxy | Upstream took too long to answer | Raise the timeout, retry on a new IP, check target latency |
Below the HTTP layer sits a second class of failures. These never produce a status code because no HTTP response ever comes back. We cover them in Connection Refused, Timeout, and Reset.
407 Proxy Authentication Required
407 is the proxy telling you it doesn't know who you are. It's the direct equivalent of a 401, except it's the proxy asking for credentials instead of the target site. The proxy answers your request with a Proxy-Authenticate header, and your client is supposed to retry with a Proxy-Authorization header.
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="sparkproxy"
Three things cause it:
- No credentials sent. You pointed at the proxy but didn't include a username and password.
- Wrong credentials. A typo, an expired sub-user, or special characters in the password that weren't percent-encoded in the URL. A password like
p@ss:wordbreaks URL parsing unless you write it asp%40ss%3Aword. - IP not whitelisted. If your provider uses IP allowlisting instead of username and password, a
407fires the moment your machine's public IP changes (DHCP renewal, a VPN toggle, a cloud instance restart).
The fix in code is to put credentials in the proxy URL so the client encodes them for you:
import requests
proxies = {
"http": "http://user:pass@proxy.sparkproxy.io:10000",
"https": "http://user:pass@proxy.sparkproxy.io:10000",
}
r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=30)
print(r.status_code, r.text)
Do not retry a 407 in a loop. The credentials won't fix themselves. For the full challenge-response flow and how Basic auth encoding works, see how proxy authentication works.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
403 Forbidden
403 is ambiguous, and that ambiguity is where most debugging time gets lost. It has two completely different meanings depending on who sent it.
403 from the proxy. Your authentication passed, but your plan doesn't permit what you asked for: a residential IP on a datacenter-only plan, a geo-restricted country, or a blocked target category. The fix is account-side. Check your plan permissions.
403 from the target. The proxy connected fine and forwarded your request, and the destination site returned 403 because it flagged the request as a bot. Same IP hitting too fast, a headless-browser fingerprint, a missing or stale cookie, or a datacenter IP the site distrusts.
You tell them apart by the response body. A proxy-level 403 usually returns a short JSON or plaintext error from your provider. A target 403 returns the site's own HTML, often a "Access Denied" or CAPTCHA page. When it's the target blocking you, rotating IP alone rarely fixes it. You also need to fix what the site sees. See how to avoid getting your proxy blocked for the fingerprint and rate factors that trigger these blocks.
429 Too Many Requests
429 means you sent more requests than a limit allows. Two different limits produce it:
- Rate limit: too many requests per second or per minute.
- Concurrency limit: too many requests in flight at the same time.
Like 403, a 429 can originate at the proxy or at the target. When the target sends it, the site is throttling your IP. Rotate to a fresh IP and slow the per-IP rate. When your proxy provider sends it, you've hit the plan's concurrency ceiling, and adding more IPs won't help until you queue fewer requests at once.
The most important thing about 429 is the Retry-After header. When present, it tells you exactly how long to wait, either in seconds or as an HTTP date:
HTTP/1.1 429 Too Many Requests
Retry-After: 8
Respect it. Backing off for the stated interval is faster in aggregate than hammering the endpoint and getting throttled harder. If there's no Retry-After, fall back to exponential backoff, covered in the retry section. For high-volume scraping, the durable fix is spreading requests across a larger pool so no single IP crosses the site's per-IP threshold.
502 Bad Gateway
502 comes from the proxy. It means the proxy acted as a gateway, forwarded your request to an upstream server (often an exit node or the target), and got back a response it couldn't parse or that was empty. The proxy did its job. The thing on the other side broke.
Common causes:
- The exit node dropped the connection to the target mid-transfer.
- The target returned a malformed response.
- A residential exit peer went offline while handling your request.
502 is transient by nature. The right response is to retry on a different IP, because the specific exit node that failed is unlikely to be the one you get next. If you see a wave of 502s across many IPs at once, the target itself is probably unhealthy, and you should back off rather than retry hard.
504 Gateway Timeout
504 is the proxy telling you the upstream took too long. The connection was established, the request went out, and no response came back inside the gateway's time budget.
The difference between 502 and 504 matters for the fix. 502 is a bad or empty response that arrived. 504 is no response arriving at all before the clock ran out. For 504:
- Raise your client-side timeout if the target is genuinely slow (heavy pages, server-side rendering).
- Retry on a new IP, since a slow or half-dead exit node can cause it.
- Check whether the target is rate-limiting by stalling. Some sites deliberately hang bot-like requests instead of returning
429.
Connection Refused, Timeout, and Reset
These three are not HTTP errors. They happen at the TCP layer, before any HTTP response exists, so there's no status code to read. Your client raises an exception instead. This is what people usually mean by a generic proxy connection error, and the specific one tells you a lot.
| Symptom | Error (errno) | What it means | Typical cause | Fix |
|---|---|---|---|---|
| Rejected instantly | Connection refused (`ECONNREFUSED`) | Host answered, but nothing is listening on that port | Wrong proxy port, proxy is down, firewall sent a reset | Verify `host:port`; confirm the proxy is up |
| Hangs, then fails | Connection timed out (`ETIMEDOUT`) | No reply arrived before the timeout | Firewall silently dropping packets, wrong host, or IP-level block | Check network and firewall rules; test the endpoint directly |
| Drops mid-request | Connection reset (`ECONNRESET`) | Connection opened, then was killed | Proxy dropped you, or the target reset a flagged session | Retry on a new IP; lower request rate |
The speed of the failure is the tell. Connection refused is instant: a machine is reachable at that address but actively rejected the port. That's almost always a wrong port number or a proxy that isn't running. If you're mixing up an HTTP port with a SOCKS endpoint, this is what you'll see. See proxy ports explained to confirm you're using the right port for the protocol.
Connection timed out is slow: your client waited the full timeout and got nothing. That points to a firewall dropping packets on the floor, an unreachable host, or an IP-level block where the target's network refuses to even acknowledge you.
Connection reset means you got in, then got kicked out mid-stream. On a proxy that's often an overloaded or cycling exit node. Against a target it can be an anti-bot layer killing the socket after it flags the request.
How to Diagnose Which Error You Have
The fastest diagnosis starts with one question: did you get an HTTP status code back at all? That single branch splits the entire problem space.
In Python's requests, the exceptions map cleanly to those transport failures, so you can branch on them:
import requests
from requests.exceptions import ProxyError, ConnectTimeout, ConnectionError, ReadTimeout
try:
r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=15)
print("status:", r.status_code)
except ConnectTimeout:
print("timed out reaching the proxy: firewall or wrong host")
except ProxyError as e:
print("proxy refused or auth failed:", e) # often wraps a 407 or refused
except ConnectionError:
print("connection refused or reset: check host:port and proxy status")
except ReadTimeout:
print("connected, but the response was too slow: raise timeout or new IP")
A quick isolation test: run the same request without the proxy. If it succeeds directly but fails through the proxy, the proxy config is the problem. If it fails both ways, the target or your network is. To confirm the proxy itself is alive and returning its own IP, see how to test if your proxy is working.
Retry and Backoff for 429 and 5xx
Not every error deserves a retry. Retrying a 407 or a 422 just repeats a guaranteed failure and can burn credits. Retry only the transient ones, and space attempts out so you don't amplify the load you're already struggling with.
| Code | Retry? | Strategy |
|---|---|---|
| 407 | No | Fix credentials or whitelist. Retrying repeats the failure. |
| 403 (plan limit) | No | Fix plan or permissions. |
| 403 (target block) | Maybe | New IP plus a better fingerprint, not a blind retry. |
| 422 / 400 / 404 | No | Fix the request. The response won't change. |
| 429 | Yes | Honor `Retry-After`, then exponential backoff with jitter. |
| 500 / 502 / 503 / 504 | Yes | Exponential backoff with jitter, rotate IP. |
The pattern that works: exponential backoff with full jitter, capped, and with Retry-After taking priority when the server sends it. Jitter matters because without it, a batch of workers that all failed at the same moment will all retry at the same moment and collide again.
import time
import random
import requests
RETRYABLE = {429, 500, 502, 503, 504}
def retry_after_seconds(resp):
val = resp.headers.get("Retry-After")
if not val:
return None
try:
return float(val) # delta-seconds form
except ValueError:
return None # HTTP-date form; parse if you need it
def backoff(attempt, base=1.0, cap=30.0):
# full jitter: random point in [0, min(cap, base * 2**attempt)]
return random.uniform(0, min(cap, base * (2 ** attempt)))
def get_with_retry(url, proxies, max_tries=5):
for attempt in range(max_tries):
try:
r = requests.get(url, proxies=proxies, timeout=30)
except (requests.exceptions.ProxyError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout):
time.sleep(backoff(attempt)) # transport failure, back off and retry
continue
if r.status_code == 200:
return r
if r.status_code == 407:
raise RuntimeError("407: fix proxy credentials, do not retry")
if r.status_code in RETRYABLE:
wait = retry_after_seconds(r) or backoff(attempt)
time.sleep(wait)
continue
r.raise_for_status() # other 4xx: surface it, don't loop
raise RuntimeError(f"gave up after {max_tries} attempts")
If you'd rather not build and maintain the retry, rotation, and fingerprint layer yourself, the SparkProxy Scraping API handles proxy selection, rendering, and anti-bot in one endpoint. It also returns a clear set of status codes, documented at sparkproxy.io/docs/scraping-api, so your retry logic can key off them directly:
| Status | Meaning (SparkProxy Scraping API) | Retry? |
|---|---|---|
| 200 | Success | No |
| 202 | Job queued (webhook mode) | No |
| 401 | Invalid or missing `X-API-Key` | No, fix the key |
| 402 | Insufficient credits | No, top up |
| 422 | Invalid parameters | No, fix the request |
| 429 | Rate or concurrency limit exceeded | Yes, with backoff |
| 503 | Service unavailable | Yes, with backoff |
| 530 | Scrape failed (timeout, CAPTCHA, and similar) | Yes, often succeeds on a new attempt |
Here's the same retry discipline against the API, keyed to those codes:
import time
import random
import requests
API = "https://scrape.sparkproxy.io/api/v1"
HEADERS = {"X-API-Key": "sk-your-key"}
def scrape(target, max_tries=4):
for attempt in range(max_tries):
r = requests.get(API, headers=HEADERS, params={
"url": target,
"render_js": "true",
"country_code": "us",
}, timeout=90)
if r.status_code == 200:
return r.text
if r.status_code == 401:
raise RuntimeError("401: bad or missing X-API-Key")
if r.status_code == 402:
raise RuntimeError("402: out of credits, top up the account")
if r.status_code == 422:
raise RuntimeError(f"422: invalid parameter -> {r.text}")
if r.status_code in (429, 503, 530):
time.sleep(random.uniform(0, min(30, 2 ** attempt)))
continue
r.raise_for_status()
raise RuntimeError("scrape failed after retries")
One detail that saves hours of confusion: when you use a scraping API, a block from the target site does not always surface as an error status. By default the API returns its own status for the scrape job, so a target that served a 403 block page can come back as a 200 whose body is the block page. The SparkProxy API exposes transparent_status_code to mirror the target's real status (available when render_js=false). If your success rate looks perfect but the extracted data is empty, check the body, not just the code.
Fixing Proxy Errors Fast
Every proxy error points somewhere specific. Start with one question: did a status code come back? A code means a server answered, and the number names the layer, whether that's auth (407), permissions or a block (403), rate limits (429), or a gateway problem (502, 503, 504). No code means a transport failure, and the exception type (refused, timeout, reset) tells you whether to look at the port, the firewall, or a dropped session.
Then sort by whether the error is worth retrying. Configuration errors like 407 and 422 need a fix, not a retry. Transient errors like 429 and the 5xx family respond to exponential backoff with jitter, with Retry-After taking priority. Get those two habits right and most proxy failures become a thirty-second diagnosis instead of an afternoon.
SparkProxy provides datacenter and residential proxies plus a managed Scraping API that returns clear, documented status codes so your error handling stays simple. See the Scraping API docs for the full status reference.
Frequently asked questions
FAQ
It means the proxy rejected your request because it couldn't verify your identity. Either no credentials were sent, the username or password was wrong (often special characters that weren't percent-encoded), or your source IP isn't on the provider's whitelist. Fix the credentials or the whitelist. Retrying without changing anything will just return another 407.
It can be either. If your proxy provider sent it, you hit the plan's rate or concurrency limit, and adding more IPs won't help until you queue fewer requests at once. If the target site sent it, that site is throttling the IP you used, so rotating to a fresh IP and slowing the per-IP rate is the fix. Check the response body and Retry-After header to tell which one you're dealing with.
A 502 bad gateway proxy error means the upstream sent back a broken or empty response. A 504 means the upstream sent back nothing at all before the timeout expired. Both come from the proxy acting as a gateway, and both are worth retrying on a new IP, but a 504 also suggests raising your client timeout if the target is genuinely slow.
Because the failure happened at the TCP layer before any HTTP response was created. Connection refused, timed out, and reset are transport errors, not status codes. Refused is instant and usually means a wrong port or a dead proxy. Timed out is slow and points to a firewall or IP block. Reset means the connection opened and then got killed mid-request.
Yes, but back off first. A 503 service unavailable means a server is temporarily overloaded or in maintenance, so it's transient. If the response includes a Retry-After header, wait exactly that long. Otherwise use exponential backoff with jitter and lower your concurrency, because retrying instantly against an overloaded server usually makes the throttling worse.
Read the response body. A proxy-level 403 typically returns a short JSON or plaintext message from your provider about plan or permission limits. A target 403 returns the site's own HTML, often an "Access Denied" or CAPTCHA page. If it's the target, rotating IP alone rarely helps. You also need to fix the fingerprint and rate that got you flagged.
Get 50% off your first purchase
Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.
Offer ends soon โ claim it before it's gone
Related articles

What Is a Datacenter ASN and Why It Matters for Proxies
A datacenter ASN is the network ID that marks an IP as hosting infrastructure, and anti-bot systems use it to flag proxies. Learn how ASN classification works.

What Is a Rotating Proxy API and How It Works
A rotating proxy API gives you one endpoint that serves a fresh IP per request or sticky sessions, so you never manage a proxy list. Here is how it works.

What Is a Proxy Gateway (Super Proxy)? How It Works
A proxy gateway is one endpoint that fans out to a whole IP pool and rotates it for you. Learn how a proxy gateway works, session control, auth, and setup.
