Skip to content

Errors & retries

When something goes wrong, the proxy answers with a status code before your request reaches the target.

A code from us is not a code from the site you are scraping. A 403 or a CAPTCHA page is a successful proxy request that the destination refused. Everything below happens earlier than that.

Code What happened What to do
400 We cannot proxy that address Send a hostname or public IP
407 We do not recognize your account Check your balance, then your credentials
451 The domain is blocked Contact support
551 No exit matched your targeting Loosen your targeting
556 You are over your request-rate limit Slow down
504 552 We tried several exits and all of them failed Retry
554 557 Something failed on our side Retry

Codes above 550 are ours. They are not defined in any RFC.

400 means the target is not something we proxy. Loopback addresses and bracketed IPv6 literals are rejected. Send a hostname or a public IPv4 address.

551 means you described a set of exits that is currently empty. Loosen the narrowest option and try again, in this order:

  1. ZIP code covers a few thousand households
  2. ASN cuts you to one carrier
  3. Device OS has no fallback
  4. City, then state

Retrying the identical request returns 551 again. Change the targeting instead.

A request that used to work can start returning 551 without your code changing. Exits join and leave constantly, and a narrow combination that had coverage an hour ago may have none now.

407 usually means an empty balance, not a wrong password. A balance that reaches zero suspends the account, and a suspended account is no longer recognized at the proxy. It fails exactly as though the credentials were wrong.

Check in this order:

  1. Your balance. Usage & statistics or the Bandwidth API.
  2. Whether options landed on the username. Targeting appends to the password. USERNAME-country-US:PASSWORD is an unknown user.
  3. Your source IP, if you use IP whitelisting. A changed egress address fails the same way.
  4. The password itself.

A sub-account is also suspended when the master account’s balance runs out. If every sub-account fails at once, check the master.

451 carries its reason in the status line, and the body is empty. Clients expose it as r.reason in Python requests or res.statusText in a browser. See Acceptable use.

556 is sent about five seconds after the request. A client timeout under five seconds turns it into a timeout and hides the cause.

504, 552, 554 and 557 are transient. Retry with a backoff, two attempts rather than ten in a loop.

We retry internally before returning any of them, so your retry sits on top of ours.

If 504 or 552 persists against one target while other targets work, that target is refusing our exits rather than the network failing. See Choosing a product.

A failure after the tunnel opens has no status code. Your TLS handshake is already in flight, and an HTTP status line cannot be written into an encrypted stream. The connection closes instead, so your client reports ECONNRESET. Retry it like a 552. Those bytes are not billed.

Over CONNECT, curl reports the tunnel result rather than the proxy’s status. %{http_code} prints 000 and the message is CONNECT tunnel failed. Use -v to see the real code:

Terminal window
curl -sv -x USERNAME:[email protected]:8000 https://example.com 2>&1 | grep '^< HTTP'

Plain HTTP on the same port returns the code normally.

import time, requests
RETRY = {504, 552, 554, 557}
TERMINAL = {400, 407, 451, 551, 556}
def fetch(url, proxies, attempts=3):
for i in range(attempts):
try:
r = requests.get(url, proxies=proxies, timeout=60)
except requests.exceptions.ConnectionError:
time.sleep(2 ** i) # tunnel closed mid-request
continue
if r.status_code in TERMINAL:
raise RuntimeError(f"{r.status_code} {r.reason}")
if r.status_code in RETRY:
time.sleep(2 ** i)
continue
return r
raise RuntimeError("out of attempts")

Set a client timeout of at least 60 seconds. We may try several exits before answering, and a 556 alone takes five.

Retrying a terminal code returns the same code.

SOCKS5 has five reply bytes, so several causes share one.

Reply Cause
0x00 Success
0x01 Credentials not recognized, or over the request-rate limit
0x04 Something failed on our side
0x05 Blocked domain, or no exit matched your targeting
0x07 0x08 Unsupported command or address type

0x01 and 0x05 each cover two causes, and one of each pair is worth retrying while the other is not. Reproduce an ambiguous SOCKS5 failure over HTTP on port 8000 to get a specific code.

Contact support with the timestamp, the entry point you connected to, and the exact targeting options you sent. The destination URL alone is not enough to find a request.

Was this page helpful?