Fix “Max Retries Exceeded with URL” in Python Requests
Quick answer
Max retries exceeded with URL is usually a requests.ConnectionError wrapping urllib3's MaxRetryError. Read the final Caused by message: fix DNS, refused connections, routing, TLS, or proxy configuration first. Add bounded exponential-backoff retries only for transient failures and operations that are safe to repeat.
The exception usually looks like this:
requests.exceptions.ConnectionError:
HTTPSConnectionPool(host='api.example.com', port=443):
Max retries exceeded with url: /v1/items
(Caused by NewConnectionError(...))Max retries exceeded with URL is a summary, not the root cause. Read the final Caused by ... text first. It normally tells you whether the failure happened during DNS lookup, TCP connection, TLS verification, proxy negotiation, or a retry policy you explicitly configured.
Important: Requests does not retry failed connections by default. Its adapter uses urllib3 internally, which can still raise an exception named
MaxRetryErrorafter the initial connection attempt exhausts the configured allowance. Do not assume the fix is simply to increase the retry count.
Diagnose the nested cause first
| Nested message or exception | What it usually means | First corrective action |
|---|---|---|
NameResolutionError, getaddrinfo failed, or Temporary failure in name resolution | DNS could not resolve the hostname | Verify the hostname and resolve it from the same machine, container, or serverless runtime |
Connection refused, Errno 111, or WinError 10061 | The host responded, but no service accepted the target port | Start the service; verify port, scheme, bind address, and container mapping |
No route to host or Network is unreachable | Routing, VPN, local-network permission, firewall, or network policy blocked the path | Test reachability from the same runtime and inspect network rules |
ConnectTimeoutError | The TCP or TLS connection was not established before the connect timeout | Check routing, firewall, server load, and the connect-timeout value |
SSLCertVerificationError or SSLError | Certificate chain, hostname, expiry, protocol, or intercepting proxy problem | Repair trust configuration; do not disable verification |
ProxyError | The configured proxy could not be reached or used | Check proxy URL, credentials, environment variables, and NO_PROXY |
ResponseError: too many 5xx responses | A custom retry policy exhausted status-code retries | Inspect server health and retry policy; respect Retry-After where applicable |
Fix deterministic connection or configuration failures before adding a retry policy for transient failures.
Step 1: verify the URL and the failing environment
Use a complete URL with the correct scheme, hostname, and port:
from urllib.parse import urlsplit
url = "https://api.example.com:443/v1/items"
parts = urlsplit(url)
if parts.scheme not in {"http", "https"} or not parts.hostname:
raise ValueError(f"Invalid HTTP URL: {url!r}")Test from the same place where Python fails. Your laptop, Docker container, CI runner, Kubernetes pod, and cloud function may use different DNS servers, routes, proxies, or certificate bundles.
# Resolve the hostname using the runtime's DNS configuration.
python -c "import socket; print(socket.getaddrinfo('api.example.com', 443))"
# Inspect DNS, TCP, TLS, redirects, and response headers.
curl -v --connect-timeout 5 https://api.example.com/v1/itemsDo not rely only on ping; many valid HTTP services block ICMP traffic.
Step 2: set connect and read timeouts
Nearly all production Requests calls should set a timeout. A tuple separates the connection phase from response inactivity:
import requests
response = requests.get(
"https://api.example.com/v1/items",
timeout=(3.05, 30), # connect timeout, read timeout
)
response.raise_for_status()The read timeout is not a deadline for downloading the entire response. Requests raises it when no bytes arrive on the socket for the configured period. A timeout prevents indefinite waiting; it does not fix DNS, routing, or certificate configuration.
Step 3: handle the correct exception type
Catch specific exceptions before their broader parent classes. SSLError and ProxyError, for example, are connection errors:
import requests
url = "https://api.example.com/v1/items"
try:
response = requests.get(url, timeout=(3.05, 30))
response.raise_for_status()
except requests.exceptions.SSLError as exc:
print(f"TLS verification failed: {exc}")
except requests.exceptions.ProxyError as exc:
print(f"Proxy connection failed: {exc}")
except requests.exceptions.ConnectTimeout as exc:
print(f"Connection timed out: {exc}")
except requests.exceptions.ReadTimeout as exc:
print(f"Server stopped sending data: {exc}")
except requests.exceptions.ConnectionError as exc:
print(f"DNS, routing, or TCP connection failed: {exc}")
except requests.exceptions.HTTPError as exc:
print(f"HTTP error response: {exc.response.status_code}")In a service, log structured fields such as hostname, exception class, attempt number, elapsed time, and status code. Do not log credentials, authorization headers, or sensitive query parameters.
Fix DNS resolution failures
For NameResolutionError or getaddrinfo failed:
- Check the hostname for spelling mistakes and unintended whitespace.
- Confirm the hostname resolves inside the failing container or runtime.
- Check VPN, split-DNS, Kubernetes DNS, and
/etc/resolv.confwhere relevant. - Verify that an HTTP proxy is not expected to perform DNS on your behalf.
- Treat a consistently nonexistent hostname as a configuration error, not a retryable outage.
Short retries can help a genuinely transient resolver failure, but repeated DNS attempts cannot repair a bad hostname.
Fix connection refused or no route to host
Connection refused normally means the network reached the host but no process accepted the requested port. Verify:
- The server process is running and listening on the expected port.
- The request uses the correct
http://orhttps://scheme. - A local service binds to an accessible interface rather than only
127.0.0.1. - Docker or Kubernetes exposes and maps the port correctly.
- Security groups, firewalls, network policies, and local-network permissions allow the path.
No route to host points earlier in the network path: routing, VPN, subnet, firewall, or host availability. Increasing max_retries only repeats the failed route.
Fix TLS certificate failures safely
Requests verifies HTTPS certificates by default. Keep that protection enabled.
For a private organizational CA, provide the trusted CA bundle:
response = requests.get(
"https://internal.example.com/health",
verify="/etc/ssl/certs/company-ca.pem",
timeout=(3.05, 30),
)Or configure it for the process:
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/company-ca.pemAlso check certificate expiry, hostname mismatch, missing intermediate certificates, system time, and TLS interception by corporate proxies. verify=False suppresses verification and makes the connection vulnerable to man-in-the-middle attacks; it is not a production fix.
Check proxy settings
Requests can read HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY from the environment. An obsolete or malformed proxy can cause every request to fail.
import requests
url = "https://api.example.com/v1/items"
print(requests.utils.get_environ_proxies(url))Do not print this mapping in shared logs if proxy URLs contain credentials. If environment proxies are unintended for a controlled runtime, disable their use explicitly:
session = requests.Session()
session.trust_env = False
response = session.get(url, timeout=(3.05, 30))Use this only when bypassing the environment proxy is allowed by your network policy.
Add retries only for transient failures
After fixing deterministic configuration problems, use a Session and HTTPAdapter for bounded retry behavior:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
def build_retrying_session() -> requests.Session:
retry = Retry(
total=5,
connect=3,
read=2,
status=3,
redirect=3,
other=0,
backoff_factor=0.5,
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=frozenset({"HEAD", "GET", "OPTIONS"}),
respect_retry_after_header=True,
raise_on_status=False,
)
adapter = HTTPAdapter(max_retries=retry)
session = requests.Session()
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
with build_retrying_session() as session:
response = session.get(
"https://api.example.com/v1/items",
timeout=(3.05, 30),
)
response.raise_for_status()This example:
- Limits total and category-specific attempts.
- Uses exponential backoff instead of immediate retry loops.
- Retries common transient statuses, including
429 Too Many Requests. - Honors
Retry-Afterwhere urllib3 supports it. - Restricts automatic retries to methods that should not create duplicate side effects.
- Returns the final HTTP response after status retries are exhausted, allowing
raise_for_status()to produce anHTTPErrorwith response context.
Choose counts and timeouts from your latency budget. Five attempts with long timeouts can turn a short upstream failure into a very slow user request.
Be careful when retrying POST, PUT, and DELETE
An HTTP operation can reach the server even if the client never receives the response. Retrying a payment, order, or message creation can therefore duplicate the side effect.
Retry POST only when the API supports an idempotency key or equivalent deduplication mechanism:
headers = {"Idempotency-Key": operation_id}
response = session.post(
"https://api.example.com/v1/orders",
json=payload,
headers=headers,
timeout=(3.05, 30),
)The server—not only the client—must enforce the idempotency guarantee.
Enable connection diagnostics temporarily
urllib3 debug logs reveal connection creation, retry increments, and response status. Enable them only while diagnosing and review logs for sensitive URLs or headers:
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger("urllib3").setLevel(logging.DEBUG)Pair the logs with request IDs and server-side traces when possible. Client retries can hide an unstable dependency unless retry counts and final outcomes are observable.
Common fixes that make the problem worse
- Blindly increasing retries: delays deterministic DNS, port, proxy, and certificate failures.
- Removing the timeout: lets the program wait indefinitely during network failure.
- Setting
verify=False: hides TLS configuration errors and removes certificate protection. - Retrying every exception: repeats programming errors and non-transient failures.
- Retrying every HTTP method: can duplicate payments, writes, emails, and other side effects.
- Using
requests.packages.urllib3: importRetryfrom the publicurllib3.utilmodule instead of Requests' historical vendored namespace. - Catching only
Exception: loses the distinction between DNS, timeout, TLS, proxy, and HTTP errors.
Authoritative references
- Requests quickstart: timeouts, errors, and exceptions
- Requests HTTPAdapter retry behavior
- Requests advanced usage: TLS certificates and proxies
- urllib3 Retry reference
This article was technically reviewed against the Requests and urllib3 documentation on August 2, 2026.
Key takeaways
- •The words max retries do not prove that Requests retried several times; Requests does not retry failed connections by default.
- •The final Caused by line is the useful diagnostic signal: it distinguishes DNS failure, connection refused, timeout, TLS failure, proxy failure, and exhausted status retries.
- •Set explicit connect and read timeouts on production requests; a timeout is not the same thing as a retry.
- •Retry transient failures with bounded exponential backoff, Retry-After support, and a narrow set of safe HTTP methods.
- •Do not disable TLS verification or blindly retry POST requests to make the exception disappear.
Frequently asked questions
What does "Max retries exceeded with URL" mean in Python Requests?
Requests could not complete the HTTP operation through its urllib3 connection pool. The message is normally wrapped in requests.exceptions.ConnectionError. The nested cause identifies whether DNS, TCP connection, routing, TLS, proxy configuration, or an explicitly configured status retry failed.
Why do I see MaxRetryError when Requests defaults to zero retries?
Requests uses urllib3 internally, and urllib3 reports MaxRetryError when the allowed connection attempts are exhausted—even when the configured retry count is zero and only the initial attempt occurred. The exception name alone is not evidence of multiple retries.
Should I increase max_retries to fix the error?
Only for transient failures such as connect timeouts, rate limits, or temporary 5xx responses. Extra retries do not repair a misspelled hostname, closed port, invalid certificate, or broken proxy, and they can make failures slower or amplify load.
How do I fix NameResolutionError or getaddrinfo failed?
Verify the hostname and scheme, test DNS resolution from the same runtime environment, and check VPN, container, DNS, and proxy settings. Retries may help a brief resolver outage but will not fix a consistently invalid hostname.
How do I fix Connection refused in Python Requests?
Confirm the service is running and listening on the requested interface and port, verify HTTP versus HTTPS, and check container port mappings and firewall rules. Connection refused normally means the host was reached but nothing accepted the TCP connection.
Can I fix an SSL error with verify=False?
Do not use verify=False as a production fix. It disables certificate and hostname verification and enables man-in-the-middle attacks. Correct the hostname or certificate chain, update the CA bundle, or configure REQUESTS_CA_BUNDLE with the trusted organizational CA.
Which HTTP methods are safe to retry?
GET, HEAD, and OPTIONS are normally safe starting points. Retry PUT or DELETE only when the API's idempotency guarantees are understood. Retry POST only when the server supports an idempotency key or another mechanism that prevents duplicate side effects.
Does the Requests timeout limit the whole download?
No. Requests documents timeout as socket inactivity thresholds, not a total wall-clock deadline for the entire response. A tuple can set separate connect and read timeouts, such as timeout=(3.05, 30).
Software Engineering Leader & Technical Author · Updated August 2, 2026