408 Request Timeout (tested)

408 Request Timeout means the server gave up waiting for the client to finish sending its request. It is not a slow-backend error (that is 504), and it is not what you see when your own client times out (that is an exception, not a status code). We made Node.js 24 send real 408s, checked which clients retry them, and measured what client-side timeouts look like instead.

1. What makes Node.js send a 408

Node.js 24.21.0 has two request timers. headersTimeout (default 60000 ms) limits how long the request headers may take; that case is measured in our keep-alive guide. requestTimeout (default 300000 ms) limits the whole request, headers and body. We set it low and sent valid headers with Content-Length: 100, then one body byte every 400 ms from a raw TCP socket:

const server = http.createServer(
  { requestTimeout: 3000, connectionsCheckingInterval: 500 },
  handler);
Client behaviourResultAfter
Headers sent, body trickled 1 byte / 400 ms (8 of 100 bytes arrived)HTTP/1.1 408 Request Timeout + Connection: close, socket closed3,012 ms
Connected, sent nothing at all408 Request Timeout + Connection: close3,501 ms
One complete request, then idle on the keep-alive socketNormal 200, then closed silently (no 408)6,004 ms

The full 408 response was just the status line and Connection: close, no body. Your handler never ran, so your logging middleware never saw these requests.

Two details we hit:

With the defaults, a client uploading a large body over a slow link gets a 408 after 5 minutes. If you accept big uploads, raise requestTimeout (or set it to 0 and enforce limits elsewhere) and cap size separately; see 413 Request Entity Too Large.

2. Do clients retry a 408?

A test server answered every request with 408 and Connection: close and counted hits:

ClientStatus seenRequests sent
curl 8.14.14081
curl 8.14.1 --retry 3 --retry-delay 14084 (curl treats 408 as transient)
Node.js 24 fetch()4081
Chromium 153, page.goto4081
Chromium 153, fetch() inside the page4081

From the Chromium source, not measured: Chromium silently retries a request when a 408 arrives on a reused keep-alive connection, treating it like a server that closed the idle socket. On a fresh connection, as in our test, it shows you the 408. Never assume a retry happened: if your API client should retry, add it yourself, and only for idempotent requests (GET, PUT, DELETE) or with an idempotency key.

3. 408 vs 504 vs client-side timeouts

What you seeWho gave upWaiting for
408 Request TimeoutThe serverThe client to finish sending the request
504 Gateway TimeoutA proxy or load balancerThe upstream app to respond
curl (28) Operation timed out after 1001 milliseconds with 0 bytes received, exit code 28Your client (--max-time 1)The response
Node.js fetch throws TimeoutError: The operation was aborted due to timeoutYour client (AbortSignal.timeout(1000))The response

The last two rows are measured against a handler that waited 5 s before answering. No status code exists in either case: the client abandoned the request. If your logs show 408s, look at the client side (slow uploads, stalled mobile networks, a client that sends Content-Length larger than the body it actually sends). If they show 504s, look at your app's response time.

4. nginx (from the docs, not measured)

nginx returns 408 when client_header_timeout (default 60s) or client_body_timeout (default 60s) expires. The body timeout is measured between two successive reads, not for the whole body, so a slow but steady upload does not trigger it. proxy_read_timeout (default 60s) is the one that produces 504 when your upstream is slow. Clients that send Expect: 100-continue wait before sending a body; see Expect: 100-continue.

5. Fixing it

Frequently asked questions

What does 408 Request Timeout mean?

The server stopped waiting for the client to finish sending its request. Node.js 24 with requestTimeout 3000 sent 408 with Connection: close at 3,012 ms to a client that sent headers and then trickled the body one byte every 400 ms.

Is 408 a client or server problem?

It is a 4xx code, so the server blames the client: the request arrived too slowly or not at all. A slow backend shows up as 504 Gateway Timeout from a proxy instead.

Does the browser or curl retry a 408 automatically?

In our test, no: curl 8.14.1, Node.js 24 fetch and Chromium 153 each sent 1 request and showed the 408. curl --retry 3 retried it, sending 4 requests. From Chromium's source, not measured: it retries silently when the 408 arrives on a reused keep-alive connection.

What is the default request timeout in Node.js?

server.requestTimeout is 300000 ms (5 minutes) and server.headersTimeout is 60000 ms in Node.js 24.21.0. Setting requestTimeout lower also lowered headersTimeout to the same value.

Is a client-side timeout a 408?

No. curl --max-time ends with error 28 and exit code 28, and fetch with AbortSignal.timeout throws a TimeoutError. No status code is received at all.

Written with AI assistance. Measured on 2026-10-02 with Node.js 24.21.0 (http.createServer on localhost:8987-8988, a raw TCP client, built-in fetch), curl 8.14.1 and headless Chromium 153.0.8010.12 (Playwright). We counted server hits to detect retries. nginx timeouts and Chromium's keep-alive retry rule are quoted from documentation and source, not measured. PlainHeaders is our extension; Node.js, curl and Chromium are free.