httpx retry: what HTTPTransport(retries=) does and doesn't retry (tested)
httpx.HTTPTransport(retries=3) retries only connection failures (ConnectError/ConnectTimeout). A ReadTimeout or a 429/503 response is not retried; handle those with a small loop that honours Retry-After.
httpx has one built-in retry setting, httpx.HTTPTransport(retries=N), and it covers less than people expect. Measured with httpx 0.28.1 on Python 3.13.5 against local http.server instances (srv.start(): the path is the seconds to wait before answering).
1. retries= retries failed connections
import httpx, socket, threading, time
# a port that refuses twice, then starts listening
port = 8966
def late_server():
time.sleep(1.2)
from srv import start
start(port)
threading.Thread(target=late_server, daemon=True).start()
for retries in (0, 3):
t = time.monotonic()
transport = httpx.HTTPTransport(retries=retries)
try:
r = httpx.Client(transport=transport).get(f"http://127.0.0.1:{port}/0")
print(f"retries={retries}: {r.status_code} after {time.monotonic() - t:.1f}s")
except httpx.ConnectError as e:
print(f"retries={retries}: ConnectError after {time.monotonic() - t:.1f}s")
Output:
retries=0: ConnectError after 0.0s
retries=3: 200 after 1.5s
The server starts listening 1.2 s in. Without retries the request fails at once with ConnectError; with retries=3 httpx reconnected with a short backoff and got the 200 after 1.5 s. Use httpx.AsyncHTTPTransport(retries=...) for AsyncClient.
2. It does not retry a read timeout
import httpx, time
from srv import start
base = start() # /3 answers after 3 s
client = httpx.Client(transport=httpx.HTTPTransport(retries=3), timeout=1.0)
t = time.monotonic()
try:
client.get(base + "/3")
except httpx.ReadTimeout:
print(f"ReadTimeout after {time.monotonic() - t:.1f}s (not retried)")
Output:
ReadTimeout after 1.0s (not retried)
Once connected, a slow response raises ReadTimeout after the 1 s timeout with no retry, even with retries=3.
3. It does not retry 429/503 either: add a small loop
import httpx, threading, http.server, time
hits = 0
class H(http.server.BaseHTTPRequestHandler):
def log_message(self, *a): pass
def do_GET(self):
global hits; hits += 1
code = 503 if hits < 3 else 200
self.send_response(code); self.send_header("Retry-After", "1"); self.send_header("Content-Length", "0"); self.end_headers()
s = http.server.ThreadingHTTPServer(("127.0.0.1", 8967), H)
threading.Thread(target=s.serve_forever, daemon=True).start()
client = httpx.Client(transport=httpx.HTTPTransport(retries=3))
print("transport retries only:", client.get("http://127.0.0.1:8967/").status_code)
def get_with_retry(client, url, attempts=4, statuses=(429, 502, 503, 504)):
for attempt in range(attempts):
r = client.get(url)
if r.status_code not in statuses or attempt == attempts - 1:
return r
wait = float(r.headers.get("Retry-After", 2 ** attempt))
print(f" attempt {attempt + 1}: {r.status_code}, sleeping {wait}s")
time.sleep(wait)
r = get_with_retry(client, "http://127.0.0.1:8967/")
print("with status retry:", r.status_code, "total hits:", hits)
Output:
transport retries only: 503
attempt 1: 503, sleeping 1.0s
with status retry: 200 total hits: 3
A 503 comes back as a normal response, so the transport returns it. The loop retries listed status codes, honours a numeric Retry-After header (otherwise exponential backoff), and returns the last response when attempts run out. Only retry idempotent requests (GET, PUT, DELETE) this way, or a POST may run twice. To also retry timeouts, wrap the client.get in try/except httpx.TimeoutException inside the same loop; libraries such as tenacity do the same with decorators.
Related: httpx timeout default, httpx vs requests, requests ConnectionError max retries.
Frequently asked questions
Does httpx retry requests automatically?
No, not by default. HTTPTransport(retries=N) retries failed connections only.
How do I retry on 429 or 503 in httpx?
Check r.status_code in a loop, sleep for Retry-After (or exponential backoff), and retry; the transport's retries= option returns the 503 unchanged.
Does httpx retries= retry ReadTimeout?
No. In the test a ReadTimeout was raised after 1.0 s with retries=3 set.
Written with AI assistance. Measured on 2026-10-23 with httpx 0.28.1 on Python 3.13.5 against local http.server instances. Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; httpx is free.