Python
requests ReadTimeout versus ConnectTimeout
Written and reviewed by Sahil Srivastav
requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='api.example.test', port=443): Read timed out. (read timeout=5)What this error actually means
Requests has no default timeout. Without one, a stalled connect or response can occupy a thread indefinitely, exhaust a worker pool, and turn one remote failure into a local outage.
A `ConnectTimeout` occurs before an established connection can be made: DNS, TCP, or TLS. A `ReadTimeout` means the connection exists but no response bytes arrived within the read window. The latter can occur after the server has accepted and acted on a POST, so blindly retrying it can duplicate work.
Timeouts are per network phase and usually apply to each read gap, not the complete response duration. A streaming endpoint can therefore run for hours while sending one byte every few seconds unless you impose an overall deadline.
Causes, most common first
- 1No timeout at all. The calling thread waits forever and consumes concurrency.
- 2Upstream handler or proxy stalls. The socket is open but response bytes stop arriving.
- 3Network path or DNS failure. Connection establishment cannot complete inside the connect budget.
- 4Pool starvation. A session waits for a pooled socket while callers interpret delay as remote slowness.
When you see it
- Requests hang until worker shutdown when no timeout is supplied
- ConnectTimeout clusters with DNS or route changes
- ReadTimeout clusters with slow upstream handlers or stalled proxies
- Retries increase upstream load during an incident
- POST outcomes are unknown after a read timeout
How to diagnose it
Step 1
Classify the exception
Inspect the exception chain and elapsed time; the phase names the failing part of the path.
python - <<'PY'
import requests
try: requests.get(url, timeout=(2, 5))
except requests.exceptions.ConnectTimeout: print('connect phase')
except requests.exceptions.ReadTimeout: print('read phase')
PYStep 2
Measure DNS, connect, and first byte
Compare curl timings to isolate the path before changing application retries.
curl -sS -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://api.example.testStep 3
Check pool wait separately
Enable urllib3 connection-pool logging and inspect active worker count; a pool wait is not a server read timeout.
The fix
Pass explicit `(connect, read)` timeouts on every call, ideally through one session wrapper.
Set an overall deadline around the operation when streaming or when business latency has a hard limit.
Retry only idempotent operations and only transient phases, with exponential backoff and jitter.
Use an idempotency key for writes whose result is unknown, and reconcile before retrying.
Bound the HTTP connection pool and expose wait time so remote latency cannot consume all workers.
timeout = (2.0, 8.0)
response = session.get(url, timeout=timeout)
response.raise_for_status()How to stop it coming back
- Lint calls that omit timeout
- Track connect, TTFB, read, and total latency separately
- Budget outbound timeouts below inbound request deadlines
- Use one retry policy per dependency
- Exercise slow headers, slow bodies, and dropped connections in tests
FAQ
What timeout should I use?
Choose it from the dependency SLA and your own deadline. A universal number is unsafe; connect is usually short, while read must cover the expected response tail.
Does read timeout mean the server failed?
No. It means no bytes arrived during the read window. The server may still be processing or may have completed a write before the client timed out.
Can I retry every ReadTimeout?
Only when the operation is idempotent or protected by an idempotency key and the retry budget is bounded.
Related
Other errors engineers hit next to this one
- java.lang.OutOfMemoryError: GC overhead limit exceeded
- java.util.ConcurrentModificationException
- OutOfMemoryError: unable to create new native thread
- RejectedExecutionException: Task rejected from ThreadPoolExecutor
- Found one Java-level deadlock (thread dump)
- FATAL: sorry, too many clients already
- Sessions stuck in "idle in transaction"
- ERROR: deadlock detected