Proxy 502 Bad Gateway and Which Side Sent It
A 502 can come from the proxy before the tunnel opens or from the website after it. Tell them apart in curl, Python and Node, then fix the right side.
By Proxies.sx Team. Reviewed .
If the 502 is the proxy's answer to CONNECT, before any TLS handshake, the proxy refused and its body names the reason. If the 502 arrives after the TLS handshake, the website or its CDN sent it through a working tunnel. Retry the first kind with different filters or after a pause. Slow down for the second kind.
In this guide
Two different 502 responses
RFC 9110 defines 502 as a response from a server acting as a gateway or proxy that "received an invalid response from an inbound server" (section 15.6.3). With a proxy in the path, two servers can act as a gateway: our proxy, and whatever load balancer or CDN sits in front of the website.
For an HTTPS URL, the client opens a tunnel with CONNECT. The proxy's answer to CONNECT arrives before the TLS handshake. Once the proxy answers 2xx it switches to forwarding encrypted bytes in both directions (section 9.3.6), and from then on it cannot write a status line into the stream. So a 502 that you can read after the handshake was produced on the website's side.
| When the 502 arrives | Who sent it | Body |
|---|---|---|
| As the answer to CONNECT, before TLS | The proxy | On our gateway, a code such as E_NO_STOCK_COUNTRY and a req id |
| After the TLS handshake, as the page response | The website, its CDN or load balancer | Whatever the site returns, often an HTML error page |
| On a plain http:// request | Either | Check the body for an E_ code to tell which |
Tell them apart with curl
curl's %{http_connect} variable holds the proxy's answer to CONNECT and %{http_code} holds the website's status (curl manual). One line of output answers the question.
We checked both cases on 19 October 2026 with local test servers and curl 8.7.1. A proxy refusing CONNECT gave connect=502 code=000. A proxy that opened the tunnel to an HTTPS server answering 502 gave connect=200 code=502.
curl -sS -o /dev/null --max-time 30 \
-x http://gw.proxies.sx:7000 \
-U 'psx_ab12cd-peer-br:YOUR_PROXY_PASSWORD' \
-w 'connect=%{http_connect} code=%{http_code}\n' \
https://example.com/
# connect=502 code=000 -> the proxy refused; read its code
# connect=200 code=502 -> the website side returned 502The same split in Python and Node
HTTP libraries usually raise an exception for a refused tunnel and return a normal response object for the website's 502. With Requests 2.32.5 and urllib3 2.6.3, the refusal raised requests.exceptions.ProxyError whose text ends in OSError('Tunnel connection failed: 502 Bad Gateway'). The website's 502 came back as a response with status_code == 502. Requests documents its proxy settings in the advanced usage guide.
Node 24's built-in fetch, with NODE_USE_ENV_PROXY=1 and HTTPS_PROXY set, gave less to work with in the same local test: it rejected with fetch failed and the cause Request was cancelled. In Node, run the curl check above to read the status. The Node fetch proxy guide covers that setup.
import time
from urllib.parse import quote
import requests
password = quote("YOUR_PROXY_PASSWORD", safe="")
proxy = f"http://psx_ab12cd-peer-br:{password}@gw.proxies.sx:7000"
proxies = {"http": proxy, "https": proxy}
def fetch(url, attempts=3):
for attempt in range(attempts):
try:
response = requests.get(url, proxies=proxies, timeout=30)
except requests.exceptions.ProxyError:
# The gateway refused the tunnel. Retry a few times with a pause.
if attempt == attempts - 1:
raise
time.sleep(2 ** attempt)
continue
if response.status_code in (502, 503, 504):
# The website side answered. Do not loop on it.
print("website returned", response.status_code)
return response
fetch("https://example.com/")Fix a 502 from the gateway
Read the code first. The error code reference has the full list.
E_NO_STOCK_COUNTRY means nothing currently matches your pool, country and filters. Check the country's count in the stock endpoint. The mobile pool (mbl) exists in six countries, so a username such as psx_ab12cd-mbl-de always fails. Use the residential pool (peer-de) for Germany. Carrier and city filters narrow the candidates further. The username guide shows how each token changes selection.
E_STOCK_DEGRADED means an endpoint went away during setup. Retry after a second or two. A sticky session picks a replacement in the same country by default.
A 590 to 599 status means the upstream device or relay refused or dropped the connection. One retry on a new connection is reasonable. If the status repeats for one session id, start a new session id. If the status is 593, the target hostname failed to resolve, so check the hostname.
503 and 504 belong to the same check
The gateway sends 503 with E_INTERNAL when an internal step fails, and a plain 503 with Gateway restarting; reconnect shortly while a process restarts. Both clear on retry. A 504 from the gateway means the upstream connection timed out (RFC 9110, section 15.6.5).
The website can send 503 and 504 too, through an open tunnel. A 503 from a site may carry a Retry-After header (RFC 9110, section 10.2.3). Honor it. The Retry-After guide has a Python implementation.
Browsers report a gateway refusal as ERR_TUNNEL_CONNECTION_FAILED and show no status at all. The tunnel failure guide covers that case.
When to stop retrying
Retrying a website's 502 in a tight loop multiplies traffic and gives the same answer. Check the site without the proxy, from your own connection. If it fails there too, the site has an outage. If it works directly and fails through several countries, slow your request rate and compare responses. The proxy suitability checklist describes a small test before scaling up.
Gateway refusals behave differently: a new connection can succeed a second later because selection runs again. Keep a retry cap anyway, and log the req id from the body of the last failure.
Common questions
Will a different proxy fix a website's 502?
The cause sits on the website's side when the 502 arrives after the TLS handshake. A different exit can get a different answer from the site's edge servers, but switching proxies does not repair the site.
Why do I see 590 or 599 instead of 502?
Those statuses come from proxy-chain, the library our gateway uses. They report which upstream step failed, such as 594 for a refused connection. Treat them like a 502 from the gateway.
Sources and test scope
Standards and vendor documentation checked on 19 October 2026. Gateway responses on these pages were observed with test requests or read from the gateway build running that day, and the client examples were run against local test servers. They do not establish current stock in a country or how a website will respond.
Connect your application to Proxies.sx
Use the username builder for gateway routing fields and the format converter for existing connection lists. Review Pool Gateway documentation, then check current mobile stock before buying traffic.
For agent-driven workflows, the agent service index lists the supported APIs. A configured HTTP client still needs to validate its task's response.
Proxy error and troubleshooting guides
Start from the symptom table, or open the guide for the error you saw.