| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | kubectl v1.37.0Envoy Gateway v1.9.2ingress-nginx controller v1.15.1Gateway API CRDs v1.6.1 (standard channel) |
| Applies to | Any host with kubectl configured; any Kubernetes cluster that already runs Envoy Gateway and ingress-nginx |
| Assumes | An existing Gateway and HTTPRoute in front of an application that accepts a POST body |
| Privilege | Normal user |
| Scope | How requestReceivedTimeout limits the time allowed to receive a request, and how that differs from ingress-nginx client-body-timeout and from a 504 response deadline. Does not cover body-size limits or keep-alive idle close. |
| Related guides | Fix an Envoy Gateway 504 Migrate proxy-read-timeout to Envoy Gateway Replace proxy-body-size on Envoy Gateway Migrate ingress-nginx to Envoy Gateway Gateway API with HTTPRoute |
A slow upload is not a slow backend:
timeouts.requestis the time allowed for the responseClientTrafficPolicytimeout.http.requestReceivedTimeoutis the time allowed to receive the request
On Envoy Gateway v1.9.2, a 2 second receive limit returned HTTP 408 and the body request timeout for 8000 bytes sent at 1000 bytes per second. The same 8000 bytes, sent without a rate limit, returned HTTP 200 and bytes=8000. The file size did not change. The pace did. A 413 is a size rejection. This 408 is a clock.
client-body-timeout is a gap between reads
bytes --1s-- bytes --1s-- bytes
No 2-second gap, so a slow steady upload can continue
requestReceivedTimeout is the total time to receive the request
start ---------------- 2s
receive timer
The whole body is not in yet, so the upload stopsSet requestReceivedTimeout
The field is on a ClientTrafficPolicy, gateway.envoyproxy.io/v1alpha1. This test targeted the Gateway:
- The timer starts when the request starts and stops when the request has been received
- It does not replace timeouts.request. Put a request deadline on the route that is long enough for the upload, or the 15 second response clock can fire as well
30s below is only so the test is not also a 504. 2s is the receive limit that produced the 408:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
name: receive-timeout
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: eg
timeout:
http:
requestReceivedTimeout: 2sIn this v1.9.2 test, that limit produced HTTP 408 and the body request timeout. The v1.9 task page describes a different status for the same field. Use the status you observe on the version you run. Do not treat 408 and that body as the result on every release.
The fast POST of 8000 bytes returned HTTP 200. Send it with no rate limit:
curl -sS -D - --max-time 20 -o upload.txt -H 'Host: app.example.com' \
--data-binary @upload.bin 'http://127.0.0.1:19080/upload'HTTP/1.1 200 OK
server: BaseHTTP/0.6 Python/3.12.15
date: Sat, 03 Oct 2026 19:20:39 GMT
content-type: text/plain
content-length: 11The same bytes at 1000 bytes per second need about 8 seconds. Send that rate-limited POST. The 408 is timestamped 19:20:41, about 2 seconds later:
curl -sS -D - --max-time 20 -o upload.txt -H 'Host: app.example.com' \
--limit-rate 1000 --data-binary @upload.bin 'http://127.0.0.1:19080/upload'HTTP/1.1 408 Request Timeout
content-length: 15
content-type: text/plain
date: Sat, 03 Oct 2026 19:20:41 GMT
connection: closeThe body is request timeout:
- There is no application
serverline - The application did not reject the body
- The receive clock closed the upload
These captures used a local address. Send the same Host and body to the hostname your Gateway publishes.
client-body-timeout is a gap, not the whole upload
ingress-nginx client-body-timeout is a different rule:
- It is the gap allowed between reads of the client body
- It is a controller ConfigMap setting, not an Ingress annotation
- The default is 60 seconds
Set to 2 seconds, the same 8000 bytes at 1000 bytes per second still returned HTTP 200 and bytes=8000. The client waited 8.005 seconds. Bytes kept arriving, so the 2 second gap never fired.
nginx client-body-timeout: 2s
bytes --1s-- bytes --1s-- bytes
ok ok
No 2-second gap, so the upload is allowed
Envoy requestReceivedTimeout: 2s
start ---------------- 2s
stop
The whole body is not received yetA stall is different from a trickle:
- With
client-body-timeoutat 2 seconds, a body that sent some bytes and then stopped was closed at 3.03 seconds with an empty read and no HTTP status line - Envoy's 408 was the slow but continuous upload
- nginx's close was the pause
Do not copy a client-body-timeout number into requestReceivedTimeout and expect the same trickle to pass.
Troubleshooting
| Symptom | Likely cause | What to change |
|---|---|---|
HTTP 408 and request timeout while the body is still uploading, on v1.9.2 |
requestReceivedTimeout is shorter than the upload |
Raise it, or send the body faster. Confirm the status on your version |
| The same file returns 200 when it is sent quickly | The failure is pace, not size | Do not raise a body-size limit for this 408 |
HTTP 504 and upstream request timeout |
The response deadline, often the 15 second default | Set timeouts.request. That does not extend a receive timeout |
| A steady slow upload works on ingress-nginx and fails on Envoy | client-body-timeout is a gap. requestReceivedTimeout is the total receive time |
Do not copy the nginx number across as if it were the same timer |
| nginx closes a paused upload with no status line | client-body-timeout saw a gap with no bytes |
That is the stall. It is not the Envoy 408 from a continuous slow send |
Summary
On Envoy Gateway v1.9.2, requestReceivedTimeout: 2s returned HTTP 408 and request timeout for 8000 bytes sent at 1000 bytes per second. The same 8000 bytes, sent without a rate limit, returned HTTP 200 and bytes=8000. The clock is how long receiving the request may take, not how large the body is.
ingress-nginx client-body-timeout: 2 still allowed that steady upload, which finished in 8.005 seconds, because the gap between reads never reached 2 seconds. A body that stopped sending was closed with no status line. Those are not the same failure as the Envoy 408, and neither one is the 504 from a slow response.
References
- Envoy Gateway Client Traffic Policy (v1.9)
- ingress-nginx ConfigMap client-body-timeout
- Envoy Gateway HTTP timeouts (v1.9)
Frequently Asked Questions
Why does a slow upload return 408 when a fast upload of the same file returns 200?
requestReceivedTimeout limits how long the proxy waits to receive the request. On v1.9.2, 2 seconds was enough for 8000 bytes sent at full speed and not enough for the same bytes at 1000 bytes per second. The fast call returned 200 and bytes=8000. The slow call returned 408 and request timeout.
Is this the same as a 413?
No. A 413 means the body was rejected for size. This 408 happened to a body that succeeded when it was sent faster. The size did not change.
Is client-body-timeout the same number?
No. It is the gap between reads of the client body. At 2 seconds, a steady 8 second upload still returned 200. requestReceivedTimeout at 2 seconds stopped that same steady upload.
Will every Envoy Gateway version return HTTP 408 for this field?
Use the status on the version you run. This v1.9.2 test returned HTTP 408 and request timeout. The v1.9 task page describes a different status for the same setting.

