2 September 2026
Retrying an HTTP request in Python, properly
Retrying an HTTP request in Python, properly
Most hand-written retry loops get three things wrong: they retry errors that will never succeed, they retry on a fixed interval, and they have no ceiling. The first wastes time, the second turns a struggling service into a failing one, and the third is how a script runs all night.
import random
import time
import requests
RETRY_ON = {408, 429, 500, 502, 503, 504}
def get(url, *, attempts=5, timeout=10):
"""GET with exponential backoff and jitter.
Retries only what a retry can plausibly fix. A 404 or a 401 will be a
404 or a 401 next time too, so those raise immediately.
"""
last = None
for attempt in range(attempts):
try:
response = requests.get(url, timeout=timeout)
if response.status_code not in RETRY_ON:
response.raise_for_status()
return response
last = requests.HTTPError(f"{response.status_code} for {url}")
# Honour Retry-After when the server sends one - it knows
# better than any backoff formula does.
retry_after = response.headers.get("Retry-After")
if retry_after and retry_after.isdigit():
time.sleep(min(int(retry_after), 60))
continue
except (requests.Timeout, requests.ConnectionError) as exc:
last = exc
if attempt == attempts - 1:
break
# Exponential, capped, with jitter. Without the jitter every
# client that failed together retries together, and the thundering
# herd is what keeps the service down.
delay = min(2 ** attempt, 30) * (0.5 + random.random())
time.sleep(delay)
raise last
The three decisions worth understanding
Retry only what a retry can fix. 408, 429 and the 5xx family are transient. A 401 means your credentials are wrong and a 404 means the thing is not there - retrying either just delays the error you were always going to get.
Jitter is not optional. If a hundred clients fail at the same instant and all back off by exactly two seconds, they all return at the same instant. The random multiplier spreads them out, and it is the single line that separates a polite client from a denial-of-service.
Respect Retry-After. When a server sends it, it is telling you
exactly when to come back. Any formula you use instead is a guess against
information you already have.
If you are using requests anyway, urllib3's built-in Retry adapter
does most of this - the value in writing it out is knowing what it is
doing on your behalf.