What does HTTP status 408 Request Timeout mean, who sends it and when, and how does it differ from 504 Gateway Timeout?
answer
- 408 = server waited for the CLIENT's request
- Send Connection: close with a 408
- Spurious 408 on idle keep-alive -> retry on a new connection
- 504 = gateway waited for UPSTREAM
- 499 (nginx) = client hung up first
basics
~20 s408 is the origin server saying it gave up waiting for the client to send a complete request in time. 504 is a gateway or proxy saying it gave up waiting for an upstream server's response. 408 blames the inbound direction, 504 the outbound one.
solid answer
~60 s**408 Request Timeout** means the server was waiting on the *client*: the request line, headers, or body did not arrive within the server's request timeout. Per RFC 9110 the server should include `Connection: close`, because it is abandoning a half-read message and cannot safely reuse the connection. In practice you see 408 in two situations. A genuinely slow or stalled client - a mobile upload on a dying link, or a slowloris-style attack dripping headers. And, far more often, a **spurious 408 on an idle keep-alive connection**: a server whose idle timeout expires sends 408 instead of just closing, and browsers and clients treat that as a signal to retry on a fresh connection. **504 Gateway Timeout** is emitted by a proxy, load balancer or API gateway that got a request fine but did not get a response from upstream in time - so the fault is behind the gateway. Related but distinct: 502 (upstream gave a broken response), 503 (this server is unavailable or overloaded), and nginx's non-standard 499 (client closed the connection before a response).
code
http · 9 linesPOST /upload HTTP/1.1
Host: api.example.com
Content-Length: 1048576
(only 4 KB of body arrives, then silence)
HTTP/1.1 408 Request Timeout
Content-Length: 0
Connection: closego deeper
Know the direction: 408 means the server waited for the client's request, 504 means a gateway waited for an upstream response.
Add the mechanics - request-read timeouts, Connection: close, and the spurious-408-on-idle-connection case that clients retry.
Use them operationally: tune request-read timeouts against slow-drip attacks, distinguish 502/503/504/499 when triaging, and stop surfacing retryable 408s as errors.
Treat these codes as observability contracts across tiers - which layer emits which, how gateway timeouts relate to upstream budgets, and what clients are entitled to retry.
## 408: the server was waiting on you A server sets a limit on how long it will wait to receive a complete request - nginx `client_header_timeout` and `client_body_timeout`, Apache's `RequestReadTimeout`, and equivalents elsewhere. If the client opens a connection and then sends the request line, headers, or body too slowly (or never finishes), the server responds `408 Request Timeout` and, per RFC 9110, includes `Connection: close`, because it has consumed part of a message and can no longer frame what would follow. Why the limit exists at all is as much about defence as tidiness. **Slowloris** attacks hold thousands of connections open by sending one header byte every few seconds; without a request-read timeout each connection occupies a worker indefinitely. A short read timeout plus 408 is the standard mitigation. ## The spurious 408 on idle connections The more common sighting has nothing to do with slow clients. A client keeps a pooled connection open; the server's keep-alive idle timeout expires; instead of closing silently, some servers emit an unsolicited `408` with `Connection: close`. A client that happens to be writing a request at that moment receives a 408 for a request the server never actually read. Because of this ambiguity, browsers and robust clients treat an immediate 408 on a reused connection as **retryable**: discard the connection, open a fresh one, resend. That behaviour is safe only because the server did not process the request. Application code should generally not surface such a 408 to users; if your logs show 408s clustering after idle gaps, the cause is the connection lifecycle, not slow clients. ## 504 and the neighbours **504 Gateway Timeout** comes from an intermediary - reverse proxy, load balancer, CDN, API gateway - which forwarded the request upstream and did not receive a response within its own timeout. The client's request was received in full; the problem is downstream of the gateway. Practically it means: look at the upstream service, its saturation, and the gateway's upstream timeout setting. Distinguishing the family: | Status | Sender | Meaning | |---|---|---| | 408 | origin server | client did not finish sending the request in time | | 502 | gateway | upstream returned an invalid or broken response | | 503 | any server | this server is unavailable/overloaded, often with `Retry-After` | | 504 | gateway | upstream did not respond in time | | 499 (nginx, non-standard) | nginx | the client disconnected before a response was sent | 499 is worth knowing because it is the mirror image of a client-side timeout: your client gave up and closed, and nginx logs 499. Seeing 499s at the edge together with slow upstream traces usually means the client deadline is shorter than the server's processing time - the fix is on one side or the other, not in the status code. ## What to do with each - **Receiving 408 as a client:** if it arrives on a reused connection with no processing implied, retry once on a new connection. Otherwise investigate whether the request body is being sent too slowly. - **Emitting 408 as a server:** keep request-read timeouts short enough to blunt slow-drip attacks, always send `Connection: close` with it, and prefer a clean close over an unsolicited 408 on idle keep-alive connections. - **Receiving 504:** the fault is upstream of the gateway; correlate the gateway's upstream timeout with the upstream's real latency, and check whether the gateway's timeout is shorter than the work actually takes.
- Why should a server send Connection: close together with a 408?Because it stopped mid-message: part of a request has been consumed and the server cannot tell where that message would have ended, so anything further on the connection could be misparsed as a new request. Closing removes that ambiguity, and it is also what prevents a request-smuggling style desynchronisation between a proxy and an origin that disagree about the stream.
- Your dashboard shows a steady trickle of 408s that correlate with idle periods rather than large uploads. What is the likely explanation?They are almost certainly spurious 408s emitted when the server's keep-alive idle timeout expired on pooled connections, rather than genuinely slow clients. The remedy is on the connection lifecycle: keep the client's idle timeout below the server's, and have clients retry once on a fresh connection. Chasing client upload speed will find nothing.
saying these in an interview costs you the question
- Saying 408 means the server itself was too slow to respond
- Confusing 408 with 504 or using them interchangeably
- Sending 408 without Connection: close and continuing to read the connection
- Treating every 408 as a client bug when idle keep-alive expiry is the usual source
- Believing 499 is a standard HTTP status code