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.

7 min readTested on requests 2.34 and 2.31
A requests ProxyError is three nested layers: requests ProxyError, urllib3 MaxRetryError and ProxyError, and the innermost exception, here NewConnectionError connection refused, which is the actual cause.One error, three layers. Read the innermost.requests.exceptions.ProxyErroralways the sameMax retries exceeded … (Caused by ProxyError('Unable to connect to proxy',always the sameNewConnectionErrorFailed to establish a new connection:[Errno 61] Connection refusedthe cause: nothing listens on that proxy portrequests 2.34 · urllib3 2.8

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://.

skip “Max retries exceeded”read the last exception in the line

The whole error arrives as one long line. This one is from a proxy URL with the wrong port:

txt
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:

diagnose.py
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:

txt
NewConnectionError - HTTPSConnection(host='127.0.0.1', port=9): Failed to establish a new connection: [Errno 61] Connection refused

The 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 ProxyErrorWhat we brokeFix
NewConnectionError … Connection refusedA port where nothing listensCheck the port. Ours are 9000 and 10000 for HTTP, 11000 and 12000 for SOCKS5.
NameResolutionError … Failed to resolveA typo in the proxy hostnameCheck the hostname; test it with ping or nslookup.
ConnectTimeoutError … timed outA port that a firewall drops, or an unreachable addressCheck host and port, then your own network: some networks block outgoing ports.
OSError('Tunnel connection failed: 407 …')Wrong password, bad username, no exit407, 403 and 465 explained
SSLError … WRONG_VERSION_NUMBER or UNEXPECTED_EOF_WHILE_READINGhttps:// in the proxy URLChange it to http://
ConnectionError … BadStatusLine('\x01\x01')An http:// proxy URL pointing at a SOCKS5 portUse the HTTP port, or socks5h:// for the SOCKS5 port.
RemoteDisconnected('Remote end closed connection without response')A proxy that closes the connection without answeringThe port isn’t an HTTP proxy, or the proxy dropped you. Check the port; retry once.
ReadTimeoutError … Read timed outhttps:// in the URL of a proxy that waits instead of answeringSame fix: http://
Fig. 1 — requests 2.34 with urllib3 2.8, October 5, 2026; the gateway rows rechecked on October 14, 2026. Error numbers such as [Errno 61] differ between operating systems.

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.

ErrorWhat we brokeFix
InvalidURL: Proxy URL had unsupported scheme htp, should use http:// or https://A typo in the schemeWrite http://
InvalidURL: Failed to parse: … is not a valid host or portA # in the passwordEscape it with urllib.parse.quote
InvalidSchema: Missing dependencies for SOCKS support.A socks5:// proxy URL without PySocksInstall 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 itNothing: a residential exit dropped during the handshakeRetry

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:

retry.py
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'?
No. urllib3 1.26 words it 'Cannot connect to proxy.' and urllib3 2 words it 'Unable to connect to proxy'. We got both from the same broken proxy URL by switching versions. The causes and fixes are identical.
What does 'Max retries exceeded with url' mean here?
Nothing about your proxy. requests retries zero times by default, so this text appears on the first failure. Skip it and read what comes after 'Caused by'.
The proxy works in curl or the browser but not in Python. Why?
Usually the Python process uses a different proxy than you think: an HTTP_PROXY or HTTPS_PROXY environment variable, which requests reads on its own and which overrides session.proxies. Print os.environ entries containing 'proxy', or set session.trust_env = False.
Should I retry a ProxyError?
Not blindly. A refused connection, an unknown hostname or a 407 will fail the same way every time. Retry the errors that aren't wrapped in ProxyError (a bare SSLError, ConnectionError or timeout), which with residential proxies usually mean one exit dropped.

Residential proxies, pay per GB

HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.