TL;DR: The Quick Fix
Most ReadTimeout errors happen because the requests library waits forever by default. To fix this, always pass an explicit timeout value in seconds. If your script is already timing out at a specific value, try bumping it up to 60 or 90 seconds to give the server more breathing room.
import requests
try:
# Use a 60-second timeout for slow API endpoints
response = requests.get('https://api.example.com/data', timeout=60)
response.raise_for_status()
except requests.exceptions.ReadTimeout:
print("The server is taking too long to respond. Try again later.")
Why is the Server Ghosting You?
Think of a ReadTimeout like being seated at a restaurant, ordering your food, and then waiting an hour without the kitchen ever bringing the appetizer. You successfully connected to the server, but it didn't send any data back within your time limit.
This differs from a ConnectTimeout, where you can't even get through the front door. If you're seeing a read timeout, the server likely received your request but got stuck. This usually happens for a few specific reasons:
- **Database Bottlenecks:** The server is running a massive SQL query that takes 45+ seconds to execute.
- **Resource Exhaustion:** The API is under heavy load (e.g., during a flash sale) and the request is sitting in a long queue.
- **Huge Payloads:** You asked for 50,000 records at once, and the server is struggling to serialize that much JSON.
- **Poor Network Quality:** High packet loss is causing the connection to stall mid-stream.
Step-by-Step Solutions
1. Use Granular Timeouts
Instead of a single number, pass a tuple to timeout. This lets you set a short window for the initial connection and a longer window for the actual data transfer. Pro tip: Use 3.05 seconds for the connection timeout, as it aligns with the TCP packet retransmission cycle.
# (connect timeout, read timeout)
# Wait 3.05s to connect, and 60s for the actual data to arrive
response = requests.get('https://api.example.com', timeout=(3.05, 60))
2. Build a Robust Retry Strategy
Network hiccups are often temporary. Rather than letting your script crash, use an HTTPAdapter to automatically retry the request with an exponential backoff. This means the script waits longer between each attempt (e.g., 0.6s, 1.2s, 2.4s) to avoid slamming a struggling server.
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def get_resilient_session(retries=3, backoff=0.3):
session = requests.Session()
retry_strategy = Retry(
total=retries,
read=retries,
connect=retries,
backoff_factor=backoff,
status_forcelist=[429, 500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
# This will retry up to 3 times before giving up
client = get_resilient_session()
response = client.get('https://api.example.com', timeout=30)
3. Thin Out the Payload
If you control the API or it supports filtering, stop asking for everything at once. Requesting 1,000 items in one go is a recipe for a timeout. Break it down into smaller chunks to keep the response time under 10 seconds.
- Implement **pagination** (e.g., `?limit=50&offset=0`).
- Use **field filtering** to only download the keys you actually need.
- Check if the server supports `Accept-Encoding: gzip` to compress the data transfer.
4. Stream Large Responses
When downloading 100MB+ files, don't wait for the whole thing to load into memory. Use stream=True to process data as it arrives. This prevents the connection from timing out while your RAM fills up.
with requests.get('https://api.example.com/big-export.csv', stream=True, timeout=60) as r:
r.raise_for_status()
for chunk in r.iter_content(chunk_size=1024 * 8):
# Process 8KB at a time
save_to_disk(chunk)
How to Test Your Timeout Logic
You don't have to wait for a real server failure to test your code. Use httpbin.org to simulate a slow response on demand.
- **Trigger a failure:** Ask the server to wait 10 seconds, but set your timeout to 5.
```
import requests try: requests.get('https://httpbin.org/delay/10', timeout=5) except requests.exceptions.ReadTimeout: print("Success: The timeout was caught!")
- **Verify a success:** Set your timeout to 15 seconds for the same 10-second delay. The request should now complete successfully with a 200 status code.
## Further Reading
- [Official Requests Guide: Timeouts](https://requests.readthedocs.io/en/latest/user/advanced/#timeouts)
- [urllib3 Retry Documentation](https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Retry)

