The requests library does not retry anything on its own. To retry, you mount urllib3's Retry on a Session, and its settings decide how long each wait is.
We built a small server that fails on purpose and logs the time of every attempt it sees. Then we timed requests 2.34.2 against it on 12 October 2026, so the waits below are measured, not worked out from a formula.
By default, nothing is retried
Our server answered 503 twice before it would answer 200. Plain requests.get() made one attempt and returned the 503. Its default adapter shows why:
Retry(total=0, connect=None, read=False, redirect=None, status=None)
The requests documentation says the same: "By default, Requests does not retry failed connections".
A Session that retries
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
retry = Retry(
total=5,
backoff_factor=0.5,
status_forcelist=[429, 500, 502, 503, 504],
)
session = requests.Session()
session.mount("http://", HTTPAdapter(max_retries=retry))
session.mount("https://", HTTPAdapter(max_retries=retry))
r = session.get("http://127.0.0.1:31293/fail/3/503?k=page1", timeout=10)
print(r.status_code)
200
The address is our test server, which failed three times here. total caps the retries, status_forcelist lists the answers worth retrying, and backoff_factor sets the waits. The server saw four attempts, and the fourth got its 200.
What backoff_factor really waits
- backoff_factor=0.5
- backoff_factor=1
The first retry came at once. After that, each wait doubled: 1 and 2 seconds with a factor of 0.5, and 2 and 4 seconds with a factor of 1. The urllib3 documentation calls it "A backoff factor to apply between attempts after the second try". Most errors, it says, clear up on a second try.
Some guides get this wrong. One puts "1, 2, 4 seconds" in a code comment for a factor of 1, where we measured 0, 2 and 4.
Retry-After on a 429
In the words of its standard, a 429 means "the user has sent too many requests in a given amount of time". The answer may carry a Retry-After header. We answered the first attempt with 429 and Retry-After: 3. With 429 in status_forcelist, the second attempt came 3.06 seconds later. urllib3 obeys the header by default. With respect_retry_after_header=False, it came after 0.05 seconds.
POST is not retried unless you allow it
With the Retry above, a POST that met a 503 was not retried at all. POST is missing from urllib3's default list, which holds DELETE, GET, HEAD, OPTIONS, PUT and TRACE. Those are the methods that are safe to repeat.
post_retry = Retry(total=5, backoff_factor=0.5, status_forcelist=[503], allowed_methods=None)
post_session = requests.Session()
post_session.mount("http://", HTTPAdapter(max_retries=post_retry))
r = post_session.post("http://127.0.0.1:31293/fail/2/503?k=page2", data={"x": "1"}, timeout=10)
print(r.status_code)
200
allowed_methods=None retries every method, and the POST went through on its third attempt. Use it only when sending the same POST twice is harmless, such as a search, and not for an order or a payment.
When the retries run out
When the server kept failing, requests raised RetryError by default. To get the last answer instead, set raise_on_status=False:
last_retry = Retry(total=2, status_forcelist=[503], raise_on_status=False)
last_session = requests.Session()
last_session.mount("http://", HTTPAdapter(max_retries=last_retry))
r = last_session.get("http://127.0.0.1:31293/fail/5/503?k=page3", timeout=10)
print(r.status_code)
503
Timeouts and dead servers
Retry also covers read timeouts on safe methods. Our server took 3 seconds to answer the first two attempts, and the client gave up after 1 second each time. The third attempt got a 200, 2.0 seconds after the first. A port where nothing listens failed for good after 3.0 seconds, the sum of its waits.
The error you get then says "Max retries exceeded", and so does a plain request with no retries set:
try:
requests.get("http://127.0.0.1:31299/", timeout=2)
except requests.exceptions.ConnectionError as e:
print(str(e)[:80])
HTTPConnectionPool(host='127.0.0.1', port=31299): Max retries exceeded with url:
The words come from urllib3, which wraps every final failure that way. The "Caused by" part that follows names the real cause.
When to switch proxy
Retrying the same proxy helps when the fault is passing. When a proxy keeps failing after its retries, try the next one:
def get_with_proxies(session, url, proxy_list, **kwargs):
for proxy in proxy_list:
try:
return session.get(url, proxies={"http": proxy, "https": proxy}, **kwargs)
except (requests.exceptions.ProxyError, requests.exceptions.ConnectionError):
continue # this proxy failed even after its retries: try the next one
raise RuntimeError("every proxy failed")
PROXIES = ["http://127.0.0.1:31299", "http://127.0.0.1:31281"] # the first one is dead
r = get_with_proxies(session, "http://127.0.0.1:31293/fail/0/200?k=page5", PROXIES, timeout=5)
print(r.status_code)
200
The first proxy was dead and raised ProxyError; the second, 3proxy on our own server, delivered the 200. A 429 is a different case: the site limits your address, so a new address helps where waiting would not. Our guide to fixing 429 errors covers when to wait and when to rotate.
Here is every case from our test, with each attempt as the server saw it:

What this page could not check
- One version of requests and urllib3, against our own server on the same machine. Real networks add their own delays.
- The proxy switch used a closed port and a local 3proxy, not a proxy network.
- We did not test jitter, the tenacity library or async clients.
- We will rerun these tests by 12 January 2027.
Sources
- urllib3 documentation, urllib3.util.Retry: urllib3.readthedocs.io.
- urllib3 2.8.0 source, Retry.get_backoff_time: github.com.
- Requests documentation, Developer Interface (HTTPAdapter): requests.readthedocs.io.
- RFC 6585, 429 Too Many Requests (April 2012): rfc-editor.org, and RFC 9110, Retry-After (June 2022): rfc-editor.org.
- Our own tests on our server, 12 October 2026: retry_tests.py at 01:14 UTC and the code on this page at 01:15 UTC, against our own fault server.



