Python requests ProxyError: Cannot connect to proxy, every cause
The message is a wrapper. The exception inside it tells you whether the host, the port, the login or the scheme is wrong.
In short
Ignore “Max retries exceeded” and “Cannot connect to proxy”; they are the same for every cause. Read the last exception in the line: NewConnectionError is the port, NameResolutionError the hostname, Tunnel connection failed the login, SSLError an https:// proxy URL that should be http://.
The whole error arrives as one long line. This one is from a proxy URL with the wrong port:
requests.exceptions.ProxyError: HTTPSConnectionPool(host='ipinfo.io', port=443): Max retries exceeded with url: /ip (Caused by ProxyError('Unable to connect to proxy', NewConnectionError("HTTPSConnection(host='127.0.0.1', port=9): Failed to establish a new connection: [Errno 61] Connection refused")))It has three layers, and only the last one changes:
The outside names the website you wanted (host='ipinfo.io'), which misleads people into debugging the website. “Max retries exceeded” is there on the first failure too, because requests retries zero times by default.
The middle says “Unable to connect to proxy” with urllib3 2 and “Cannot connect to proxy.” with urllib3 1.26. Same error, different wording.
The inside is the exception that happened. Here it is NewConnectionError … Connection refused, and the host and port in it are the proxy’s, not the website’s.
To get just that part in code:
import os
import requests
USER = os.environ["PROXY_USER"]
PASS = os.environ["PROXY_PASS"]
proxy = f"http://{USER}:{PASS}@via.proxies.vision:9000"
try:
r = requests.get("https://ipinfo.io/ip", proxies={"http": proxy, "https": proxy}, timeout=30)
print(r.status_code, r.text.strip())
except requests.exceptions.ProxyError as e:
cause = e.args[0].reason.original_error # the exception urllib3 wrapped
print(type(cause).__name__, "-", cause)With the wrong port from above, the last line prints:
NewConnectionError - HTTPSConnection(host='127.0.0.1', port=9): Failed to establish a new connection: [Errno 61] Connection refusedThe inner exception, cause by cause
We broke a working proxy URL in one way at a time and noted what requests put inside the ProxyError.
| Inside the ProxyError | What we broke | Fix |
|---|---|---|
| NewConnectionError … Connection refused | A port where nothing listens | Check the port. Ours are 9000 and 10000 for HTTP, 11000 and 12000 for SOCKS5. |
| NameResolutionError … Failed to resolve | A typo in the proxy hostname | Check the hostname; test it with ping or nslookup. |
| ConnectTimeoutError … timed out | A port that a firewall drops, or an unreachable address | Check host and port, then your own network: some networks block outgoing ports. |
| OSError('Tunnel connection failed: 407 …') | Wrong password, bad username, no exit | 407, 403 and 465 explained |
| SSLError … WRONG_VERSION_NUMBER or UNEXPECTED_EOF_WHILE_READING | https:// in the proxy URL | Change it to http:// |
| ConnectionError … BadStatusLine('\x01\x01') | An http:// proxy URL pointing at a SOCKS5 port | Use the HTTP port, or socks5h:// for the SOCKS5 port. |
| RemoteDisconnected('Remote end closed connection without response') | A proxy that closes the connection without answering | The port isn’t an HTTP proxy, or the proxy dropped you. Check the port; retry once. |
| ReadTimeoutError … Read timed out | https:// in the URL of a proxy that waits instead of answering | Same fix: http:// |
Refused, timed out, not resolved
These three mean your request never reached a proxy. Nothing about your login has been checked yet, so don’t reset your password. Compare host and port with your dashboard character by character. On our gateway a wrong port doesn’t refuse the connection, it lets it time out, so a timeout is the more likely symptom of a typo in the port.
It works elsewhere, not in Python
requests reads HTTP_PROXY, HTTPS_PROXY and ALL_PROXY from the environment. A stale value there produces this error from code with a perfectly good proxy URL, or with none. For a Session, the environment even wins over session.proxies. Run env | grep -i proxy, and set session.trust_env = False if you want your code to be the only source.
Errors that aren’t ProxyError
Some proxy mistakes are caught earlier, or surface later, under another name.
| Error | What we broke | Fix |
|---|---|---|
| InvalidURL: Proxy URL had unsupported scheme htp, should use http:// or https:// | A typo in the scheme | Write http:// |
| InvalidURL: Failed to parse: … is not a valid host or port | A # in the password | Escape it with urllib.parse.quote |
| InvalidSchema: Missing dependencies for SOCKS support. | A socks5:// proxy URL without PySocks | Install requests[socks] |
| ConnectionError: ('Connection aborted.', ConnectionResetError(…)) | A proxy URL pointing at a TLS port, with http:// | Use the proxy’s plain-HTTP port |
| SSLError … UNEXPECTED_EOF_WHILE_READING, no ProxyError around it | Nothing: a residential exit dropped during the handshake | Retry |
No error, wrong page
For http:// websites a refused login doesn’t raise anything. The proxy’s 407 comes back as the response, and your code carries on with the proxy’s error page. Check r.status_code.
A ProxyError says the proxy is unreachable or refused you, and it will do so again on the next attempt. Retrying only hides the real problem behind a longer wait.
Errors without that wrapper are different. With residential proxies, each request leaves through someone’s home connection, and now and then one goes away mid-request. That shows up as a bare SSLError, ConnectionError or timeout, and the next request, through another exit, works. This helper retries those and lets a ProxyError through at once:
import os
import time
import requests
USER = os.environ["PROXY_USER"]
PASS = os.environ["PROXY_PASS"]
proxy = f"http://{USER}:{PASS}@via.proxies.vision:9000"
proxies = {"http": proxy, "https": proxy}
def get(url: str, attempts: int = 3) -> requests.Response:
for attempt in range(attempts):
try:
return requests.get(url, proxies=proxies, timeout=30)
except requests.exceptions.ProxyError:
raise # the proxy refused or is unreachable: retrying won't help
except (requests.exceptions.SSLError, requests.exceptions.ConnectionError,
requests.exceptions.Timeout):
if attempt == attempts - 1:
raise
time.sleep(attempt + 1) # a dropped exit: the next request gets another one
print(get("https://ipinfo.io/ip").text.strip())Order matters
ProxyError is a subclass of ConnectionError. If the except ProxyError clause comes second, it never runs and every wrong password is retried three times.
Is 'Unable to connect to proxy' a different error from 'Cannot connect to proxy'?
What does 'Max retries exceeded with url' mean here?
The proxy works in curl or the browser but not in Python. Why?
Should I retry a ProxyError?
Keep reading
aiohttp Proxy Errors: ClientHttpProxyError 407, SOCKS5 and trust_env
aiohttp's ClientHttpProxyError explained, why its message leaks your proxy password, socks5:// without aiohttp-socks, and rotation.
6 min read · #python#errors
httpx: unexpected keyword argument 'proxies' — Fix Without Downgrading
httpx 0.28 removed proxies=. What replaces it (proxy= and mounts=), the OpenAI SDK case, SOCKS5 and environment proxies.
5 min read · #python#errors
Scrapy: Could not open CONNECT tunnel with proxy (407, 403, SOCKS5)
What the status in Scrapy's TunnelError means, why socks5:// only times out, and a proxy middleware that works. Every case reproduced.
7 min read · #scrapy#python
Residential proxies, pay per GB
HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.