How to Fix Max Retries Exceeded With URL in Python
Name the failure hidden behind the wrapper, then fix it or let requests retry it with sensible waits.
Updated
TL;DR
Max retries exceeded with url means urllib3, the library under Python requests, stopped trying one request; with requests' defaults that happens after one failed attempt. Fix the error named after Caused by, often a host name that does not resolve or a port where nothing listens.
What Max retries exceeded looks like
Captured on macOS; Linux prints [Errno 111] for a refused port and [Errno -2] for an unknown name.
| Where | What you see |
|---|---|
| requests 2.33 with urllib3 2.6, host name does not resolve | requests.exceptions.ConnectionError: HTTPSConnectionPool(host='api.example.invalid', port=443): Max retries exceeded with url: /v1/items (Caused by NameResolutionError("HTTPSConnection(host='api.example.invalid', port=443): Failed to resolve 'api.example.invalid' ([Errno 8] nodename nor servname provided, or not known)")) |
| requests 2.31 with urllib3 1.26, nothing listens on the port | requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8000): Max retries exceeded with url: /health (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x1045e48c0>: Failed to establish a new connection: [Errno 61] Connection refused')) |
| requests with a Retry that allows read retries, server never answers | requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=52812): Max retries exceeded with url: / (Caused by ReadTimeoutError("HTTPConnectionPool(host='127.0.0.1', port=52812): Read timed out. (read timeout=1)")) |
| requests with Retry(status_forcelist=[503]), every reply is 503 | requests.exceptions.RetryError: HTTPConnectionPool(host='127.0.0.1', port=41426): Max retries exceeded with url: /status (Caused by ResponseError('too many 503 error responses')) |
| urllib3 2.6 used directly, after its default 3 retries | urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='127.0.0.1', port=8000): Max retries exceeded with url: / (Caused by NewConnectionError("HTTPConnection(host='127.0.0.1', port=8000): Failed to establish a new connection: [Errno 61] Connection refused")) |
| requests 2.33 with Retry(total=2, connect=2) and logging.basicConfig() on | WARNING:urllib3.connectionpool:Retrying (Retry(total=1, connect=1, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError("HTTPConnection(host='127.0.0.1', port=62286): Failed to establish a new connection: [Errno 61] Connection refused")': / |
Why this happens
urllib3 ran out of attempts, and the message wraps the error that stopped it.
Unless you mount your own Retry, requests allows urllib3 zero retries, so the first refused connection, failed DNS lookup or TLS error raises MaxRetryError. Its reason, printed after Caused by, is the error that ended the last attempt.
Diagnose your Max retries exceeded first
The exception names in the traceback tell you which step failed and whether retrying can help.
Open the error's host and path in a browser on this computer; if it loads, the server is up and your script's setup differs.
Read the class after Caused by; NameResolutionError means the host name did not resolve, and NewConnectionError with Connection refused means nothing listens on that port.
Read the class before HTTPSConnectionPool; ProxyError means the hop to your proxy failed, and SSLError means the TLS handshake failed.
Add logging.basicConfig() before the request; each Retrying line is one extra attempt, and no such line means requests tried only once.
If Caused by shows ResponseError('too many 503 error responses'), the server did answer, and your Retry's status_forcelist turned its replies into the error.
Solutions ranked by effectiveness
Correct the address first; retries help only when the same call works on a later attempt.
- Most common fix
Correct the host, port or scheme
Applies when Caused by names NameResolutionError, Connection refused or WRONG_VERSION_NUMBER. These fail the same way on every attempt, so fix the URL or the server before adding retries.
Compare host and port in HTTPSConnectionPool(...) with the address the server really uses.
NameResolutionError: correct the spelling; [Errno -3] instead means DNS fails where the script runs.
Connection refused: start the server, or change the URL to the port it listens on.
WRONG_VERSION_NUMBER: write http:// for a port that does not speak TLS.
- Check next
Count connect, read and status retries apart
Applies when the same call works on a later try, such as during a server restart. Give each failure type its own count, and catch RequestException: a RetryError is not a ConnectionError.
# pip install requests from requests import RequestException, Session from requests.adapters import HTTPAdapter from urllib3.util import Retry retry = Retry(total=5, connect=3, read=1, status=2, backoff_factor=1, status_forcelist=[502, 503, 504]) session = Session() session.mount("https://", HTTPAdapter(max_retries=retry)) try: print(session.get("https://example.com/", timeout=(5, 30)).status_code) except RequestException as err: print(type(err).__name__, "caused by", type(getattr(err.args[0], "reason", err)).__name__) - For developers
Keep the last reply when retries end
Applies when status retries run out on 5xx replies. raise_on_status=False returns the final response, so your code can read its status and Retry-After header instead of catching RetryError.
# pip install requests import requests from requests.adapters import HTTPAdapter from urllib3.util import Retry retry = Retry(total=3, status_forcelist=[502, 503, 504], backoff_factor=1, raise_on_status=False) session = requests.Session() session.mount("https://", HTTPAdapter(max_retries=retry)) r = session.get("https://example.com/", timeout=(5, 30)) print(r.status_code, "Retry-After:", r.headers.get("Retry-After"))
Stop the Max retries exceeded from coming back
Habits that keep retries honest and the real cause visible in your logs.
Reuse one Session per worker, so requests keeps connections open instead of connecting and resolving again for each call.
Leave POST out of allowed_methods unless the API ignores duplicates; a retried read can run it twice.
Pin urllib3 in requirements; release 2.6 changed how connections print in these errors, which can break log parsers.
Track len(r.raw.retries.history) on successful responses; a rising count warns before calls start failing.
Keep backoff_factor small enough that the waits fit your job; urllib3 caps a single wait at 120 seconds.
Related errors
Learn more
FAQ
Keep your Retry, add a proxy
Set session.proxies once; the Retry mounted on that session also covers proxied calls.


