HTTP 408 Request Timeout

Request Timeout

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-probes closes 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.

Try it in the HTTP Status Codes

408 Request Timeout

  1. 408Request Timeout4xx Client error

    The server gave up waiting for the rest of the request on this connection.

    When to use it

    Slow clients that stop mid-request, and idle connections the server is reclaiming.

    Common mistakes

    Sending it when the server or an upstream was the slow one — that is 503 or 504 — and reading a run of them as an attack: browsers open connections ahead of time, and some sit idle until the server gives up on them.

    Not cacheable by defaultRFC 9110

Every code, by class

Codes marked unofficial come from one server’s own convention — nginx, Cloudflare, cPanel, Microsoft — and are not in the IANA registry.

Runs in your browser. Nothing you enter is sent anywhere.