Quick verdict: cURL has two separate timeouts. --max-time is the maximum time for the whole request (DNS + connect + TLS + body transfer). --connect-timeout is just the TCP+TLS handshake. Defaults: --max-time is UNLIMITED (which is why your scripts hang); --connect-timeout defaults to 300 seconds. For production scripts, always set --max-time 30 --connect-timeout 10 as a sensible baseline.
This guide covers what each timeout does, the meaning of error 28 (operation timed out), how proxies affect timeouts, retry patterns, and 6 working examples.
The Two Timeouts
| Flag | What it covers | Default | When it triggers error 28 |
|---|---|---|---|
--max-time | Whole request (DNS + connect + TLS + body) | Unlimited | Server is slow to respond |
--connect-timeout | TCP + TLS handshake only | 300 seconds | Server is unreachable / network blocked |
--speed-time + --speed-limit | Low transfer rate | Off | Bytes/sec stays below threshold |
6 Working Examples
1. Sensible production defaults
curl --max-time 30 --connect-timeout 10 https://api.example.com
2. Short timeout for liveness checks
curl -I --max-time 5 --connect-timeout 3 https://example.com -o /dev/null -w '%{http_code}
'
3. Long timeout for large downloads
curl --max-time 600 --connect-timeout 15 https://example.com/large.iso -O
4. Low-speed timeout (abort if transfer below threshold)
# Abort if transfer drops below 1 KB/s for 30 seconds
curl --speed-time 30 --speed-limit 1024 https://example.com/large.zip -O
5. Through a proxy with timeouts
curl --max-time 60 --connect-timeout 20 -x http://USER:PASS@proxy.spyderproxy.com:8080 https://api.example.com
Notice connect-timeout is 20s, longer than direct calls — through a residential proxy, the handshake adds 100-300ms RTT, so allow more headroom.
6. Retry on timeout
curl --retry 3 --retry-delay 5 --max-time 30 --connect-timeout 10 https://api.example.com
By default, cURL retries on transient errors (timeout, connection-refused, 5xx). For 4xx errors, add --retry-all-errors if you want to retry those too.
Decoding Error 28
cURL prints two distinct error 28 messages:
- "Connection timed out after N milliseconds" →
--connect-timeoutexceeded. Server is unreachable, port blocked, or DNS slow. - "Operation timed out after N milliseconds with X bytes received" →
--max-timeexceeded. Server connected fine but didn't finish the response in time.
Diagnose by separating the budgets: set --connect-timeout 5 --max-time 30. If you get connect-timeout error, it's a network/DNS issue; if max-time, it's a slow server.
Proxy Effects on Timeouts
Adding a proxy typically increases:
- Connect time by 50-300 ms (extra TCP+TLS handshake to the proxy)
- Per-request RTT by 50-200 ms (proxy adds latency)
- Throughput variability (proxy capacity varies)
Recommended timeouts for proxied requests:
| Proxy type | --connect-timeout | --max-time |
|---|---|---|
| Direct (no proxy) | 5-10 s | 15-30 s |
| Static datacenter | 10-15 s | 30-60 s |
| Rotating residential | 15-25 s | 45-90 s |
| LTE mobile | 20-30 s | 60-120 s |
Verify proxy latency before launching scrapers with our proxy checker tool — it tests handshake and request latency in 5 seconds.
Production Patterns
Liveness check (fast fail)
curl -s -o /dev/null -w '%{http_code}' --max-time 5 --connect-timeout 3 https://example.com/health
API call with retry
curl --retry 3 --retry-delay 2 --max-time 30 --connect-timeout 10 -H 'Authorization: Bearer xyz' https://api.example.com/items
Long download with low-speed timeout
curl --max-time 1800 --speed-time 60 --speed-limit 102400 https://example.com/big-file.iso -O
Allows up to 30 minutes total, but aborts if transfer drops below 100 KB/s for 60 seconds (probably hung).