How HTTP Works
4xx · Client error
< HTTP/1.1 408 Request Timeout

408 Request Timeout

408 Request Timeout means the server gave up waiting for the client to finish sending. Tune nginx, Apache, Node and ALB timeouts and tell it apart from 504.

Reviewed 5 min readintermediate5 sourcesTry itMarkdown
Cacheable
Only with explicit freshness
Retry?
Yes: the request can be repeated unchanged
Usually sent by
Web server or proxy (idle connection)
Spec
RFC 9110 §15.5.9
Often confused with
504
On this page

TL;DR: 408 Request Timeout means the server waited too long for your client to finish sending the request. Check upload stalls and request-read timeouts, then retry on a fresh connection when the operation is safe to repeat.

RFC 9110 section 15.5.9: the server “did not receive a complete request message within the time that it was prepared to wait”. It usually sends Connection: close, and a client “MAY repeat that request on a new connection”. Some servers send 408 on an unused connection, including connections opened by browser preconnect. Match the log entry to an actual failed request before treating it as an upload failure. MDN describes unused connections.

An illustrative HTTP/1.1 response:

HTTP/1.1 408 Request Timeout
Connection: close
Content-Length: 0

What you see in your client

These are the client formats for a response carrying 408 and the reason phrase Request Timeout; the URLs are placeholders. They are derived from library source, rather than captured server output. A server-supplied reason phrase can change the Python, .NET and Spring text.

  • Axios: AxiosError: Request failed with status code 408. With the default status validation, the promise rejects; inspect error.response.status, error.response.data and error.response.headers. Axios constructs this message in settle.
  • fetch: the promise resolves when the HTTP response arrives. response.status === 408 and response.ok === false; check those before reading a success payload. A network failure rejects separately. MDN documents this distinction.
  • Python requests: requests.exceptions.HTTPError: 408 Client Error: Request Timeout for url: https://api.example.com/resource. This comes from response.raise_for_status(), not from requests.get() alone. Save the body before raising if it contains useful diagnostics. Requests source.
  • curl -f: curl: (22) The requested URL returned error: 408. curl -i shows headers and the error body without fail mode; --fail-with-body keeps the body while returning a failing exit code. curl source, option documentation.
  • .NET: System.Net.Http.HttpRequestException: Response status code does not indicate success: 408 (Request Timeout). This is the English message from EnsureSuccessStatusCode(); inspect the HttpResponseMessage first when you need its body. Runtime message template.
  • Spring: WebClientResponseException with message 408 Request Timeout from GET https://api.example.com/resource for WebClient.retrieve(). RestTemplate’s default error handler uses HttpClientErrorException; its message can include the response body. These class names follow the Spring 6.2 WebClient source and HTTP client source.

Who sent it?

EvidenceLayer
nginx log client timed out (110: Connection timed out) while reading client request headers or ... request bodynginx client_header_timeout / client_body_timeout
Apache body Request Timeout - Server timeout waiting for the HTTP request from the client.; log AH01382: Request header read timeout or AH01382: Request body read timeoutmod_reqtimeout
ALB access log elb_status_code 408 and target status -ALB: client did not send a complete request within the idle timeout (60 s default)
Node returns it with no handler involvementhttp.Server requestTimeout (300 s default since Node 18) or headersTimeout (minimum of 60 s and requestTimeout)
Empty/unfinished request line and no user errorA connection opened without a complete request; correlate with client activity

Fix it, most common first

  1. Slow or large uploads on a poor link. Raise the body timeout or upload in chunks (resumable uploads, S3 multipart, tus). Reducing the payload (compression, image resize) is better than a long timeout, which also leaves connections open longer under slow-client attacks.
  2. Client opens a connection and sends nothing. Browser preconnect and speculative connections cause 408s in access logs for requests nobody made. Correlate them with an empty request line and client activity before filtering alerts.
  3. Keep-alive races behind ALB. Match the log status first. A target closing its connection before ALB expects it can cause 502. Set the target keep-alive timeout above the ALB idle timeout to address that separate failure, rather than treating every closed socket as 408.
  4. Proxy buffering a slow upload. nginx reads the full body before proxying by default; a slow client trips client_body_timeout, not your app’s timeout.
  5. Client-side tools. Postman and curl have own timeouts (curl --max-time, --connect-timeout) that report errors differently, as curl: (28), not a 408.

Common causes by stack

  • nginx: an incomplete header or stalled body exceeds the client-read timeout; inspect the client timed out error-log context and the directives below. Both client-read defaults are 60 seconds. nginx core module.
  • Apache: mod_reqtimeout expires its header/body read budget or minimum data rate. Check AH01382, then the RequestReadTimeout rule. Apache source.
  • Express on Node.js: the HTTP server parser can emit 408 before Express receives the request. Inspect server.headersTimeout and server.requestTimeout rather than a route handler or database query timeout. Node HTTP timeouts.
  • AWS ALB: the client sends no data within the configured idle interval. TCP keep-alives do not reset it; AWS requires at least one byte of data within each interval. Check elb_status_code against target_status_code in the access log. AWS troubleshooting.

Fix by stack

nginx

http {
    client_header_timeout 15s;   # default 60s
    client_body_timeout   120s;  # default 60s; time between two successive reads, not total
    send_timeout          60s;
    keepalive_timeout     75s;
}

A header-read timeout closes the nginx request with status 408; the client may receive a closed connection rather than a response body. Match the access log with the error-log context. nginx request handling.

Apache

LoadModule reqtimeout_module modules/mod_reqtimeout.so
RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500

Node.js and Go

const server = http.createServer(app)
server.keepAliveTimeout = 65_000   // above ALB idle timeout (60 s)
server.headersTimeout   = 66_000   // chosen header-read budget; independent of idle keep-alive
server.requestTimeout   = 120_000  // whole request, 408 if exceeded; 0 disables

Go uses socket read deadlines; configure their header, whole-request and idle budgets separately:

srv := &http.Server{
    ReadHeaderTimeout: 10 * time.Second,
    ReadTimeout:       60 * time.Second,
    IdleTimeout:       75 * time.Second,
}

AWS ALB

Raise the load balancer attribute idle_timeout.timeout_seconds (default 60) only if clients really send slowly; otherwise fix the client. ALB 408s count under HTTPCode_ELB_4XX_Count.

Client handling

Treat 408 like a connection error: retry the request on a new connection with backoff. It is safe only when the method is idempotent or the server never began processing; for POST add an Idempotency-Key where the API supports one. Cap attempts and keep a record of the final status.

Reproduce and verify

# Send a complete header and stall the body: use a local test server
(printf 'POST /upload HTTP/1.1\r\nHost: localhost\r\nContent-Length: 100\r\n\r\npartial'; sleep 90) \
  | nc localhost 8080

# A continuous trickle tests total request time, not nginx gaps between reads
curl -v --limit-rate 100 -T bigfile.bin https://example.com/upload

408 vs 504 and 524

  • 408: the client was too slow sending the request.
  • 504 Gateway Timeout: a proxy waited too long for the upstream response.
  • 524: Cloudflare connected to the origin but received no response within its proxy read timeout (125 seconds by default).
  • 429: the client sent too many requests, not too slowly.
  • 400: the request arrived but was invalid.

Try timeouts hands-on in the request builder.

Frequently asked questions

What does 408 Request Timeout mean?

The server did not receive a complete request within the time it was prepared to wait and is closing the connection (RFC 9110 section 15.5.9). The delay is on the client's side of the connection.

Why do I see 408 in my nginx or ALB logs when users report no errors?

Some servers send 408 on unused connections opened by browser preconnect. Correlate an empty or incomplete request line with client activity before alerting; a log entry need not represent a failed page load.

What is the difference between 408 and 504?

408 means the server waited too long for the client to send its request. 504 means a gateway or proxy waited too long for the upstream server's response. 524 is Cloudflare's version of the latter, after 125 seconds by default.

Is it safe to retry after a 408?

RFC 9110 allows the client to repeat the request on a new connection. It is safe for idempotent methods; for POST use an idempotency key supported by that API, since the server may have begun processing a partially received body.

How do I fix 408 errors behind an AWS ALB with Node.js?

For ALB-generated 408, inspect stalls in client data and the ALB idle timeout, whose default is 60 seconds. A Node target closing keep-alive connections too early is a separate 502 cause; set its keepAliveTimeout above the configured ALB idle timeout.

Sources

  1. MDN Web Docs: 408 Request Timeoutdeveloper.mozilla.org
  2. RFC 9110 section 15.5.9: 408 Request Timeoutrfc-editor.org
  3. nginx: client_body_timeout and client_header_timeoutnginx.org
  4. Apache httpd: mod_reqtimeouthttpd.apache.org
  5. Node.js: http.Server timeoutsnodejs.org

Keep going

Browse /search