← Debugging Guides

阅读中文版本

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.

intermediatedebugginghttptimeoutnetworking

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 -i

Interpretation

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.