HTTP 408 Request Timeout — Client Sent Too Slowly
Diagnose 408 Request Timeout by raising client-send timeouts, keeping connections alive, and preventing slow-loris style delays.
Symptoms
- The server returns 408 Request Timeout for uploads on slow links
- Requests fail only over mobile or high-latency networks
- Logs show "client timed out while reading request headers"
Possible causes
- The client takes too long to send the full request headers or body
- A server or proxy send timeout is smaller than real client latency
- A slow-loris style connection is deliberately trickling data
Diagnosis steps
Reproduce with an artificially slow client
curl --limit-rate 1k --max-time 60 -X POST https://api.example.com/upload --data-binary @big.bin -iInterpretation
A 408 under rate limiting points to a send timeout rather than a payload problem.
Check server timeout settings
grep -rn "client_header_timeout\|send_timeout\|request_timeout" /etc/nginx/Interpretation
Timeouts below real client latency produce spurious 408s; low is not always safe.
Root cause
The server closes a connection with 408 when the client does not finish sending the request within the configured timeout. This protects against slow-loris attacks but also penalizes genuinely slow clients.
Fix
Raise client send timeouts to match real latency, keep keepalive sensible, use chunked uploads for large bodies, and rate-limit at the edge rather than only timing out slow clients.
Risk note
Very long timeouts widen the slow-loris attack window; combine generous timeouts with connection and request-rate limits.
Verify the fix
Retry the slow upload and confirm it completes without 408; verify a deliberately abusive slow connection is still rejected.
Symptoms
This guide profiles the failure behind “HTTP 408 Request Timeout — Client Sent Too Slowly”. It walks through the observable symptoms, a reproducible diagnosis path, the underlying root cause, the concrete fix, and how to verify the change and prevent a regression.
Observed symptoms include:
- The server returns 408 Request Timeout for uploads on slow links
- Requests fail only over mobile or high-latency networks
- Logs show “client timed out while reading request headers”
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Reproduce with an artificially slow client
curl --limit-rate 1k --max-time 60 -X POST https://api.example.com/upload --data-binary @big.bin -i
A 408 under rate limiting points to a send timeout rather than a payload problem.
Step 2: Check server timeout settings
grep -rn "client_header_timeout\|send_timeout\|request_timeout" /etc/nginx/
Timeouts below real client latency produce spurious 408s; low is not always safe.
Likely contributors, in order of frequency:
- The client takes too long to send the full request headers or body
- A server or proxy send timeout is smaller than real client latency
- A slow-loris style connection is deliberately trickling data
Root Cause
The server closes a connection with 408 when the client does not finish sending the request within the configured timeout. This protects against slow-loris attacks but also penalizes genuinely slow clients.
The Fix
Raise client send timeouts to match real latency, keep keepalive sensible, use chunked uploads for large bodies, and rate-limit at the edge rather than only timing out slow clients.
Watch out: Very long timeouts widen the slow-loris attack window; combine generous timeouts with connection and request-rate limits.
# nginx: tune client send timeouts
client_header_timeout 30s;
client_body_timeout 30s;
send_timeout 30s;
keepalive_timeout 65s;
# rate limit abusive clients at the edge
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;
Verify and Prevent
Retry the slow upload and confirm it completes without 408; verify a deliberately abusive slow connection is still rejected.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.