HTTP 408 is Request Timeout: the server didn’t receive a complete request within the time it was prepared to wait (RFC 9110 section 15.5.9). The slow party is the client, or the network between them, not the server. RFC 9110 lets the client repeat a request it had in flight, on a new connection if this one can no longer be used.
Where they come from
- Idle preconnects. Browsers open connections ahead of time in case you click, and some never carry a request. HAProxy’s documentation describes these ending in a 408 when its timeout fires first, and its
option http-ignore-probescloses them silently instead. - Stalled uploads, such as a phone that loses signal halfway through sending a body.
- Packets that never arrive. HAProxy’s docs name an MTU mismatch, often with a VPN on the path, as the most common cause of a request that never shows up; it hits POSTs and GETs with large cookies.
Who sends it
nginx client_header_timeout, client_body_timeout (60s each) logs 408 and closes without a response
Node.js server.headersTimeout (60s), server.requestTimeout (300s) sends 408, then closes
HAProxy timeout http-request sends 408, then closes
nginx’s client_body_timeout runs between two reads, not over the whole upload, so a slow but steady upload doesn’t trip it. Node’s requestTimeout covers the entire request, so a large upload over a slow link needs a higher value.
What clients do
Chrome retries a request that gets a 408 on a reused HTTP/1.1 keep-alive connection, treating it as a stale connection; it doesn’t for HTTP/2 or HTTP/3, which multiplex and have no need for 408. In your own client, retry an idempotent request once on a fresh connection. Retry a POST only if the server deduplicates it.
408 or 504
the server waited for the client to finish sending 408 Request Timeout
a proxy waited for the upstream to answer 504 Gateway Timeout
A 408 in your logs is about your visitors’ connections. When users report requests timing out, look for 504s, or for a client-side timeout that never reached the server at all.
Gotchas
- A burst of 408s isn’t necessarily an attack. Preconnects and keep-alive timeouts produce them in normal traffic.
- Caching. 408 isn’t heuristically cacheable.
- Timeout attacks. Short header timeouts are a defense against slowloris-style clients that trickle headers to hold connections open; don’t raise them just to silence the log.
client_header_timeout 15s;
client_body_timeout 30s; # between two reads of the body, not for the whole upload
const server = http.createServer(app); // Node
server.headersTimeout = 20_000;
server.requestTimeout = 600_000; // the whole request, uploads included
Search 408 504 in the tool below to compare the two.