< HTTP/1.1 408 Request Timeout408 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.
- 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; inspecterror.response.status,error.response.dataanderror.response.headers. Axios constructs this message insettle. - fetch: the promise resolves when the HTTP response arrives.
response.status === 408andresponse.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 fromresponse.raise_for_status(), not fromrequests.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 -ishows headers and the error body without fail mode;--fail-with-bodykeeps 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 fromEnsureSuccessStatusCode(); inspect theHttpResponseMessagefirst when you need its body. Runtime message template. - Spring:
WebClientResponseExceptionwith message408 Request Timeout from GET https://api.example.com/resourceforWebClient.retrieve(). RestTemplate’s default error handler usesHttpClientErrorException; its message can include the response body. These class names follow the Spring 6.2 WebClient source and HTTP client source.
Who sent it?
| Evidence | Layer |
|---|---|
nginx log client timed out (110: Connection timed out) while reading client request headers or ... request body | nginx 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 timeout | mod_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 involvement | http.Server requestTimeout (300 s default since Node 18) or headersTimeout (minimum of 60 s and requestTimeout) |
| Empty/unfinished request line and no user error | A connection opened without a complete request; correlate with client activity |
Fix it, most common first
- 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.
- 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.
- 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.
- 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. - Client-side tools. Postman and curl have own timeouts (
curl --max-time,--connect-timeout) that report errors differently, ascurl: (28), not a 408.
Common causes by stack
- nginx: an incomplete header or stalled body exceeds the client-read timeout; inspect the
client timed outerror-log context and the directives below. Both client-read defaults are 60 seconds. nginx core module. - Apache:
mod_reqtimeoutexpires its header/body read budget or minimum data rate. CheckAH01382, then theRequestReadTimeoutrule. Apache source. - Express on Node.js: the HTTP server parser can emit 408 before Express receives the request. Inspect
server.headersTimeoutandserver.requestTimeoutrather 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_codeagainsttarget_status_codein 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
Related
460 Client Closed Connection (AWS ALB)
An AWS ALB 460 means the client disconnected before the load balancer idle timeout. Confirm it in access logs, then compare client and target timings.
400 Bad Request
400 Bad Request means the server could not parse your request. Find which layer sent it, fix bad JSON and oversized cookies or headers, and reproduce with curl.
405 Method Not Allowed
405 Method Not Allowed means the URL exists but not for that method. Read the Allow header, then fix redirects turning POST into GET, static hosting and WAFs.
406 Not Acceptable
The server cannot produce a response matching the client's Accept headers. Learn about content negotiation and how to handle format mismatches.