Proxy errors vs target errors
We pass target responses through unchanged. The reliable way to know who generated an error: check for the X-Proxy-Reason header.
X-Proxy-Reasonpresent → we generated the response (407, 451, 502, 503, 504).X-Proxy-Reasonabsent → the target generated it (403, 429, 5xx).
distinguish.pypython
import requests
proxy = "http://USER:[email protected]:8080"
r = requests.get("https://target.com/", proxies={"http": proxy, "https": proxy},
timeout=20)
if "X-Proxy-Reason" in r.headers:
print("Our error:", r.status_code, r.headers["X-Proxy-Reason"])
else:
print("Target error:", r.status_code)
The full error reference is at /docs/errors.
407 — fixing auth
407 always means the proxy couldn't authenticate your request. Common causes:
- Wrong credentials — copy-paste error or stale password. Regenerate credentials in the dashboard and update your config.
- URL-encoding — if your username or password contains special characters (
@,:,/,+), they must be percent-encoded in the URL form. - Wrong auth method — some libraries require
Proxy-Authorization: Basic BASE64instead of URL-embedded credentials. See the authentication article. - Order expired — if your bandwidth is exhausted, new requests get 407 until you place another order.
url-encode-special-chars.pypython
from urllib.parse import quote
user = quote("user@domain", safe="") # %40
pwd = quote("p@ss:w0rd!", safe="") # %40 %3A %21
proxy = f"http://{user}:{pwd}@gw.justproxies.online:8080"
403 — target blocking
A 403 with no X-Proxy-Reason means the target rejected the request. Common reasons and fixes:
- ASN/datacenter block — the target recognises the IP as belonging to a datacenter and blocks it. Switch from the datacenter pool to residential or mobile.
- IP was previously flagged — on rotating pools each retry automatically pulls a fresh IP. Retry up to 3–4 times before giving up.
- Missing headers — some targets require a realistic
User-Agent,Accept-Language, orReferer. Add them. - TLS fingerprint detection — the target uses JA3/JA4 fingerprinting to detect automation. See TLS fingerprinting.
retry-403.pypython
import requests, time, random
PROXY = {"http": "http://USER:[email protected]:8080",
"https": "http://USER:[email protected]:8080"}
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
def fetch(url: str, max_attempts: int = 4) -> requests.Response:
for attempt in range(max_attempts):
r = requests.get(url, proxies=PROXY, headers=HEADERS, timeout=20)
if r.status_code != 403:
return r
# Each retry gets a different IP on rotating pools.
wait = 0.5 * (2 ** attempt) + random.uniform(0, 0.5)
time.sleep(wait)
raise RuntimeError(f"Persistent 403 on {url} after {max_attempts} attempts")
A 403 that persists across 4+ retries almost always means the target rejects the pool type (datacenter vs residential), not the specific IP. Upgrade the pool before increasing retry count further.
429 — rate limited
The target is enforcing a request rate limit per IP or per session. Strategy:
- Rotating pool: drop any sticky session token, retry immediately — the next IP starts with a fresh rate limit budget.
- Sticky session: respect the
Retry-Afterheader if present, or back off for 10–30 seconds before retrying on the same IP. - Persistent 429 across many IPs: reduce concurrency. You are hitting an account-level or ASN-level limit, not a per-IP one.
handle-429.pypython
import time, requests
def fetch_with_429_handling(url: str, session: requests.Session) -> requests.Response:
for attempt in range(5):
r = session.get(url, timeout=20)
if r.status_code != 429:
return r
# Respect Retry-After if present; otherwise exponential backoff.
retry_after = r.headers.get("Retry-After")
wait = float(retry_after) if retry_after else min(30, 2 ** attempt)
time.sleep(wait)
raise RuntimeError(f"Still rate-limited after 5 attempts: {url}")
When to upgrade the pool
Target blocks that don't clear after retries are a pool mismatch, not a configuration error. Upgrade criteria:
- Datacenter → Residential: persistent 403 on rotating datacenter, clears after switching. The target ASN-detects datacenter IPs.
- Residential → Mobile: persistent 403 on residential, target uses carrier detection or device fingerprinting. Mobile IPs have the highest trust score.
- Rotating → Static: target requires a consistent IP for a trusted relationship (e.g., API key tied to IP allowlist). Static datacenter IPs are stable for a calendar month.
Pool capabilities are compared on /products.