Main Ecosystems
HomePython CorePandas ReferenceNumPy ScientificFastAPI & PydanticDjango Enterprise
More Ecosystems
Environment & SetupRequests & HTTPAsyncIO ConcurrencyObject-Oriented OOPPyTorch Deep LearningScikit-Learn MLFlask FrameworkWeb ScrapingDatabase & ORMDevOps & Docker

Python Requests/urllib3: Resolving Persistent Timeout and ReadTimeout Errors

Verified FixPython 3.10+Requests 2.31+, urllib3 2.0+Silo: requests

Quick Fix / Solution Rapide

This error occurs when Python sends HTTP requests that exceed socket connect or read time limits. Specify an explicit timeout tuple timeout=(connect_timeout, read_timeout) such as timeout=(3.05, 30.0).

Root Cause Analysis

This error occurs when Python code making HTTP calls via requests or urllib3 exceeds the allocated time limit waiting for the server to accept the connection or send response bytes, raising requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout, or a standard TimeoutError.

Root Cause 1: Conflating Connect Timeout and Read Timeout

Passing a single float like timeout=5.0 applies 5 seconds to BOTH the initial TCP connection handshake and the time between individual bytes received. If a remote server accepts the connection immediately (0.1s) but takes 6 seconds to execute a database query before streaming data, timeout=5.0 aborts with a ReadTimeout.

Root Cause 2: Slow Remote Backend Database Queries or Heavy Payloads

Endpoints generating large CSV exports or running complex analytics take significant time to produce the first byte.

Root Cause 3: Network Packet Drops and TCP Retransmissions

On unstable network connections, packet loss causes TCP backoff, exceeding small hardcoded timeout values.

Root Cause 4: Thread or Event Loop Starvation

In high-concurrency applications, CPU saturation delays socket processing, prompting internal timers to trigger timeout exceptions prematurely.

Reproduction Code (MCVE)

Example: Bug Reproduction
# Simulating socket timeout when reading from slow endpoint
def execute_http_request(timeout_tuple: tuple):
    connect_t, read_t = timeout_tuple
    # If read timeout is impractically low (< 0.05s), simulate socket timeout
    if read_t < 0.05:
        raise TimeoutError(
            f"HTTPSConnectionPool: Read timed out. (read timeout={read_t}). "
            "Server took longer to send response bytes than allotted read timeout."
        )
    return {"status_code": 200, "data": "OK"}

# Executing request with insufficient read timeout triggers TimeoutError
execute_http_request((3.05, 0.01))

Solution 1: Pass Explicit Connect and Read Timeout Tuples

Provide a two-element tuple (connect_timeout, read_timeout) to allocate a short connection window and an adequate read duration.

Example: Recommended Solution
import requests

# Solution 1: Recommended timeout tuple configuration
# Connect timeout: 3.05s (slightly greater than a multiple of 3 for TCP SYN retries)
# Read timeout: 30.0s (gives remote server adequate time to process complex requests)
recommended_timeout = (3.05, 30.0)

def safe_api_call_simulation(timeout_config: tuple):
    assert len(timeout_config) == 2
    assert timeout_config[0] > 3.0
    assert timeout_config[1] >= 10.0
    return {"status": "configured_properly", "timeout": timeout_config}

result = safe_api_call_simulation(recommended_timeout)
print("Timeout configuration validated:", result)
assert result["status"] == "configured_properly" 

Solution 2: Implement Exponential Backoff Retries on Timeout

Use urllib3.util.retry.Retry to retry requests that encounter transient read timeouts.

Example: Alternative Solution
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
import requests

# Solution 2: Retrying on timeouts
session = requests.Session()
retries = Retry(
    total=3,
    backoff_factor=2,
    read=3,       # Retry on read timeouts
    connect=3,    # Retry on connect timeouts
    status_forcelist=[504]
)
adapter = HTTPAdapter(max_retries=retries)
session.mount("https://", adapter)

print("Session retry adapter mounted successfully.")
assert adapter.max_retries.read == 3

Never set timeout=None in production unless you are intentionally maintaining a permanent streaming connection (such as Server-Sent Events / SSE). Setting timeout=None means Python will hang forever if the remote server drops the connection without closing the socket.

Contrast ConnectTimeout with ReadTimeout: ConnectTimeout happens during the initial TCP/TLS handshake before the server receives the request; ReadTimeout happens after the server receives the request but delays transmitting the response bytes.