How to Fix requests.exceptions.ProxyError in Python
A map from each Caused by message to its cause, plus three Python fixes, most common first.
Updated
TL;DR
requests.exceptions.ProxyError means Python requests could not get through its proxy, which was unreachable, refused the CONNECT tunnel or spoke another protocol, so no website saw the request. The fastest fix is to correct whatever the error after Caused by names, usually the proxy host, port or URL scheme.
What ProxyError looks like in each client
Captured on macOS, so Linux and Windows print other errno numbers; the conda row comes from conda's source.
| Where | What you see |
|---|---|
| requests 2.33 with urllib3 2.6 (proxy host name does not resolve) | requests.exceptions.ProxyError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: / (Caused by ProxyError('Unable to connect to proxy', NameResolutionError("HTTPSConnection(host='proxy.invalid', port=9000): Failed to resolve 'proxy.invalid' ([Errno 8] nodename nor servname provided, or not known)"))) |
| requests 2.31 with urllib3 1.26 (nothing listens on the proxy port) | (Caused by ProxyError('Cannot connect to proxy.', NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x101aaeab0>: Failed to establish a new connection: [Errno 61] Connection refused'))) |
| requests with urllib3 2.x (proxy answers CONNECT with 502) | (Caused by ProxyError('Unable to connect to proxy', OSError('Tunnel connection failed: 502 Bad Gateway'))) |
| requests with urllib3 2.x, raised as requests.exceptions.ConnectionError (proxy closes the connection without answering CONNECT) | ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) |
| pip 24.0 (bundled urllib3 1.26, as in pip up to 26.0) | WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x102392750>: Failed to establish a new connection: [Errno 61] Connection refused'))': /simple/requests/ |
| conda (raised when requests reports a ProxyError) | Conda cannot proceed due to an error in your proxy configuration. |
Why this happens
requests talks to the proxy first, and that first step failed.
urllib3, the library under requests, wraps most failures on the way to the proxy, such as a refused connection or a 407 reply to CONNECT, in its own ProxyError. The second item inside ProxyError(...) names the real cause.
Diagnose your ProxyError first
Each check reads one part of the Caused by text and names the cause or the page that covers it.
Look for your proxy's host in the Caused by part; another host there means requests took its proxy from environment or system settings.
If the inner error is NameResolutionError or NewConnectionError, requests never reached a proxy at that host and port; ConnectTimeoutError means a proxy timeout.
If it says Tunnel connection failed: 407, the proxy wants a valid login; other codes are refusals explained on the ERR_TUNNEL_CONNECTION_FAILED page.
If it says Your proxy appears to only use HTTP, your https:// proxy URL reached a plain HTTP port; the EPROTO page covers this.
If the inner error is RemoteDisconnected or ConnectionResetError, the proxy hung up without answering CONNECT; one cause is an http:// URL on a SOCKS5 port.
Solutions ranked by effectiveness
Start with the proxy URL itself, then with proxies that requests picks up on its own.
- Most common fix
Re-enter the proxy host and port
Applies when requests never got to your own proxy, for example after a typo in host or port. Copy both again from your dashboard into one http:// URL for both keys.
# pip install requests import requests proxy = "http://USERNAME:PASSWORD@proxy.geonode.io:9000" proxies = {"http": proxy, "https": proxy} try: r = requests.get("https://example.com/", proxies=proxies, timeout=30) print(r.status_code) except requests.exceptions.ConnectionError as err: reason = getattr(err.args[0], "reason", err.args[0]) inner = getattr(reason, "original_error", reason) print(type(err).__name__, "->", type(inner).__name__, inner) - Check next
Keep only the proxy your code sets
Applies to any variant above when the proxy in the error is not yours. requests adds HTTPS_PROXY, ALL_PROXY or OS proxy settings ahead of session.proxies; trust_env = False turns that off.
# pip install requests import requests url = "https://example.com/" proxy = "http://USERNAME:PASSWORD@proxy.geonode.io:9000" print("Ignoring:", requests.utils.get_environ_proxies(url)) session = requests.Session() session.trust_env = False session.proxies = {"http": proxy, "https": proxy} print(session.get(url, timeout=30).status_code) - For developers
Match the URL scheme to the port
Applies when the proxy hangs up on CONNECT. A SOCKS5 port does not speak HTTP, so a proxy URL's scheme must name the protocol of its port.
Use http:// with Geonode's HTTP ports, 9000-9010 rotating or 10000-10900 sticky.
For SOCKS5 ports 11000-11010 or 12000-12010, run pip install "requests[socks]" first.
Then write socks5h:// so the proxy, not your machine, resolves site names.
Stop the ProxyError from coming back
Habits that keep stale proxy variables out of your requests and every proxy failure in one handler.
Clear both spellings of a proxy variable: Python prefers https_proxy over HTTPS_PROXY, and an empty https_proxy disables both.
After trust_env = False, pass verify= and auth yourself; the session also stops reading REQUESTS_CA_BUNDLE and .netrc.
Remember that NO_PROXY, in the environment or as proxies["no_proxy"], filters only environment proxies, never one set in code.
Log the inner exception's class, not its text; urllib3 1.26 and 2.x word the same failure differently.
Catch requests.exceptions.ConnectionError as well, so a proxy hang-up or a SOCKS failure reaches the same handler.
Related errors
Learn more
FAQ
Measure proxy latency in Python
Geonode's test script, built on requests, reports average and median latency through your proxies.


