http.client timeout in Python: defaults and per-read limits (tested)
http.client.HTTPConnection waits forever unless you pass timeout=, and even then the timeout isn't what most people expect. We measured the default, the TimeoutError you get, and a slow server that never trips the timeout.
1. No timeout by default, and where it triggers
import http.client, socket, threading, time
# slow server: accepts, sends headers, then stalls 3s before the body
srv = socket.create_server(("127.0.0.1", 8965))
def slow():
for _ in range(3):
c, _ = srv.accept(); c.recv(4096)
c.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n")
time.sleep(3); c.sendall(b"hello"); c.close()
threading.Thread(target=slow, daemon=True).start()
print("default timeout is socket._GLOBAL_DEFAULT_TIMEOUT:",
http.client.HTTPConnection("127.0.0.1", 8965).timeout is socket._GLOBAL_DEFAULT_TIMEOUT,
"| socket default:", socket.getdefaulttimeout())
for t in (None, 1):
conn = http.client.HTTPConnection("127.0.0.1", 8965, timeout=t)
t0 = time.monotonic()
try:
conn.request("GET", "/")
r = conn.getresponse() # headers arrive at once
print(f"timeout={t}: status {r.status} after {time.monotonic()-t0:.1f}s", end="; ")
print(f"body {r.read()!r} after {time.monotonic()-t0:.1f}s")
except Exception as e:
print(f"{type(e).__module__}.{type(e).__name__}: {e} after {time.monotonic()-t0:.1f}s")
Output:
default timeout is socket._GLOBAL_DEFAULT_TIMEOUT: True | socket default: None
timeout=None: status 200 after 0.0s; body b'hello' after 3.0s
timeout=1: status 200 after 0.0s; builtins.TimeoutError: timed out after 1.0s
Measured on Python 3.13.5: HTTPConnection without timeout= uses the global socket default, which is None: no timeout at all. Against a server that stalled for 3 seconds before the body, the call simply waited 3 seconds; a server that never answers would hang your program forever. With timeout=1, the status line arrived immediately and the stalled read() raised TimeoutError: timed out after 1.0s. Since Python 3.10, socket.timeout is the same class as the built-in TimeoutError, so either name catches it.
2. The timeout is per socket operation, not for the whole request
import http.client, socket, threading, time
# trickling server: one body byte every 0.8s, 6 bytes total
def trickle(port):
srv = socket.create_server(("127.0.0.1", port))
def run():
c, _ = srv.accept(); c.recv(4096)
c.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n")
try:
for ch in b"abcdef":
time.sleep(0.8); c.sendall(bytes([ch]))
except BrokenPipeError:
pass # client gave up
c.close()
threading.Thread(target=run, daemon=True).start()
trickle(8966)
conn = http.client.HTTPConnection("127.0.0.1", 8966, timeout=1)
t0 = time.monotonic()
conn.request("GET", "/")
body = conn.getresponse().read()
print(f"timeout=1 but read() took {time.monotonic()-t0:.1f}s and returned {body!r}")
# a total deadline: check the clock between chunks
def get_with_deadline(host, port, path, total):
deadline = time.monotonic() + total
conn = http.client.HTTPConnection(host, port, timeout=min(total, 1))
conn.request("GET", path)
r = conn.getresponse()
chunks = []
while chunk := r.read1(65536):
chunks.append(chunk)
if time.monotonic() > deadline:
conn.close()
raise TimeoutError(f"took longer than {total}s in total")
return b"".join(chunks)
trickle(8967)
t0 = time.monotonic()
try:
get_with_deadline("127.0.0.1", 8967, "/", total=2)
except TimeoutError as e:
print(f"deadline version: TimeoutError: {e} after {time.monotonic()-t0:.1f}s")
t0 = time.monotonic()
try:
http.client.HTTPConnection("10.255.255.1", 80, timeout=2).request("GET", "/")
except TimeoutError as e:
print(f"connect to unroutable host: TimeoutError: {e} after {time.monotonic()-t0:.1f}s")
Output:
timeout=1 but read() took 4.8s and returned b'abcdef'
deadline version: TimeoutError: took longer than 2s in total after 2.4s
connect to unroutable host: TimeoutError: timed out after 2.0s
Measured: a server that sent one byte every 0.8 seconds never tripped timeout=1, because each wait was under 1 second; the read took 4.8s in total. The timeout limits each blocking socket call (connect, each recv), not the whole request. For a real total limit, check a deadline between chunks as in get_with_deadline(), which gave up after 2.4s for a 2s budget (the overshoot is up to one chunk wait). A connect to an unroutable address raised TimeoutError after the same 2s timeout. HTTPSConnection takes the same timeout= argument.
For urllib.request.urlopen(url, timeout=...), see our URLError guide for how urllib wraps (or doesn't wrap) these timeouts.
Frequently asked questions
What is the default timeout for http.client.HTTPConnection?
None (no timeout) unless you changed socket.setdefaulttimeout(). We measured the default being socket._GLOBAL_DEFAULT_TIMEOUT with the socket default None, and the request waiting for a stalled server.
What exception does http.client raise on timeout?
TimeoutError: timed out (socket.timeout is the same class since Python 3.10). We measured it on a stalled read and on a connect to an unroutable host.
Why doesn't my http.client timeout stop a slow download?
The timeout applies to each socket operation. We measured a server sending one byte every 0.8s finishing in 4.8s with timeout=1. Use a total deadline checked between chunks.
How do I set a total timeout for an http.client request?
Track a deadline with time.monotonic() and check it between read1() chunks, with a small per-operation timeout. We measured it stopping after 2.4s for a 2s budget.
Written with AI assistance. Measured on 2026-10-09 on Debian with Python 3.13.5's http.client against local scripted servers: the default timeout, a server stalling 3s, a server trickling one byte every 0.8s, a total-deadline helper, and a connect to an unroutable address. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; Python's http.client is built in.