| 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 ingress-nginx and Envoy Gateway |
| Assumes | An existing ingress-nginx deployment with an HTTP workload, and an Envoy Gateway with an HTTPRoute for the same application |
| Privilege | Normal user |
| Scope | What replaces nginx.ingress.kubernetes.io/proxy-body-size on Envoy Gateway, why requestBuffer.limit buffers the whole request, and why connection.bufferLimit is not that upload cap. Does not cover request timeouts, or a body limit inside the application. |
| Related guides | Fix an Envoy Gateway 504 Migrate ingress-nginx to Envoy Gateway Gateway API with HTTPRoute Expose services with Ingress Kubernetes Services |
If an Ingress uses nginx.ingress.kubernetes.io/proxy-body-size, there is no matching annotation to copy onto an HTTPRoute. Migrate ingress-nginx to Envoy Gateway does not carry that annotation across. On Envoy Gateway v1.9, a request body limit is BackendTrafficPolicy requestBuffer.limit.
The defaults are not the same either:
- ingress-nginx defaults
proxy-body-sizeto1m, which configures NGINXclient_max_body_size. - Envoy Gateway does not add
requestBufferto an HTTPRoute unless you configure it. - With no such policy, the tested route accepted a 2097152 byte upload that ingress-nginx's default
1mlimit rejected.
To keep an upload cap, add the policy and target the application HTTPRoute or the Gateway.
An oversized body is still HTTP 413. A custom error page can change the text, so the status alone is the portable fact. In this test the two bodies differed:
- Envoy's request-buffer rejection was plain text
Payload Too Large. - ingress-nginx returned an HTML page titled
413 Request Entity Too Large.
What replaces proxy-body-size in Envoy Gateway?
proxy-body-size and requestBuffer.limit both end in HTTP 413 for an oversized body. They are not the same mechanism.
| ingress-nginx | Envoy Gateway |
|---|---|
nginx.ingress.kubernetes.io/proxy-body-size: "10m" |
requestBuffer.limit: 10Mi |
proxy-body-size configures client_max_body_size |
requestBuffer.limit enables full request buffering and sets the buffered-body limit |
| Set on one Ingress | Target that HTTPRoute, or the Gateway for every route on it |
Default 1m when the annotation is absent |
No requestBuffer until you add the policy |
| Oversized body returns HTTP 413 | Oversized buffered body returns HTTP 413 |
10m and 10Mi are the same byte count here. ingress-nginx 1m allowed 1048576 bytes and rejected 1048577, so m on that controller is 1024 × 1024. 10Mi is 10 times that.
This is a behavior change, not a field rename:
proxy-body-sizesets NGINXclient_max_body_size.- Envoy Gateway's
requestBufferturns on full request buffering and uses that buffer as the maximum body size.
The annotation you are leaving behind looks like this. Do not copy it onto the HTTPRoute. Envoy Gateway does not read ingress-nginx annotations:
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"ingress-nginx translates that annotation into client_max_body_size. The examples below assume an existing HTTPRoute named web that forwards to your application Service.
How Envoy Gateway requestBuffer.limit works
requestBuffer.limit is not a renamed proxy-body-size. On Envoy Gateway v1.9, enabling it makes Envoy receive the complete request before forwarding it. Memory grows with the number of requests buffered at once. It is the wrong setting for streaming and upgrade traffic, including gRPC streaming and WebSocket. Use it when you want the gateway to enforce a maximum body size.
Once the policy is attached, the buffer behaves like this:
- A body at the limit is forwarded to the application.
- A body over the limit stops in the buffer and returns HTTP 413. The application does not see that request.
- On v1.9.2 the request buffer object has one field,
limit. There is no separate mode switch. - The limit counts the received body, not only the
Content-Lengthheader. A chunked body of 1024 bytes against a1Kilimit returned 200, and 1025 bytes returnedPayload Too Large.
requestBuffer waits for the complete request body and should not be used for streaming or upgrade traffic such as gRPC streaming and WebSocket. WebSocket disconnects covers that case on an upgraded connection.
Set a request body limit on one HTTPRoute
Target the policy at HTTPRoute web when only that application should be capped. 10Mi is a stand-in. Replace it with the maximum body your application should accept:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: request-buffer
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: web
requestBuffer:
limit: 10MiTwo fields do the work:
targetRefschooses the route. A Gateway target instead applies to every route on that Gateway.requestBuffer.limitis the cap, and it is also what turns buffering on.
The boundary below was checked with limit: 1Ki, so one extra byte is visible without an 11MiB upload. The attached policy reported Accepted=True:
kubectl get backendtrafficpolicy request-buffer -o jsonpath=\
'{range .status.ancestors[*].conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'Accepted=True AcceptedAccepted=True means the proxy took the policy. It does not mean a large upload has been tried yet.
Verify the upload limit
With this requestBuffer.limit configuration, a body equal to the limit reached the application, while one byte over returned HTTP 413. These captures used a local address for the Gateway. Send the same Host and body to the hostname your Gateway publishes.
With limit: 1Ki, a 1024 byte POST returned 200 and the application counted the body:
curl -sS -D - --max-time 15 -H 'Host: app.example.com' \
--data-binary @upload-1024.bin http://127.0.0.1:19080/uploadHTTP/1.1 200 OK
server: BaseHTTP/0.6 Python/3.12.15
date: Sat, 03 Oct 2026 19:52:57 GMT
content-type: text/plain
content-length: 11
bytes=1024server: BaseHTTP/0.6 Python/3.12.15 means the application answered. The next body is 1025 bytes:
curl -sS -D - --max-time 15 -H 'Host: app.example.com' \
--data-binary @upload-1025.bin http://127.0.0.1:19080/uploadHTTP/1.1 413 Payload Too Large
content-length: 17
content-type: text/plain
date: Sat, 03 Oct 2026 19:52:56 GMT
connection: close
Payload Too LargeRead three things in that response:
- HTTP 413 is the body-size rejection.
- Plain text
Payload Too Largecame from Envoy's request buffer in this test. Treat it as the result here, not as a rule for every 413. - No application
serverline means the application did not see this body.
ingress-nginx proxy-body-size: "1k" drew the same line: 1024 bytes returned 200, and 1025 bytes returned the nginx HTML 413 page.
Apply the request body limit to every route
Point targetRefs at the Gateway when every route should share the cap. The limit is still the maximum you chose. 10Mi below is the same stand-in as on the route:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: request-buffer
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: eg
requestBuffer:
limit: 10MiA Gateway target with requestBuffer.limit: 1Ki returned the same Payload Too Large response for a 1025 byte body, and that policy was also Accepted=True. Choose the target by scope:
- Gateway target when every route should share the cap.
- HTTPRoute target when only one application should be limited.
Why Envoy accepts an upload that ingress-nginx rejected
Do not assume the old nginx upload limit is still in force after the Ingress is gone. With no requestBuffer policy, Envoy Gateway accepted a 2097152 byte POST. curl received 100 Continue, then the application counted every byte:
curl -sS -D - --max-time 30 -H 'Host: app.example.com' \
--data-binary @upload-2m.bin http://127.0.0.1:19080/uploadHTTP/1.1 100 Continue
HTTP/1.1 200 OK
server: BaseHTTP/0.6 Python/3.12.15
date: Sat, 03 Oct 2026 19:51:56 GMT
content-type: text/plain
content-length: 14
bytes=2097152That result shows this route accepted at least 2MiB. It does not show that every body size will succeed. Another filter that buffers a request can still return 413 when its own buffer is exceeded.
The same host through ingress-nginx, with the default body limit, allowed 1048576 bytes. One more byte was HTTP 413. The page in this test was nginx HTML:
curl -sS -D - --max-time 30 -H 'Host: app.example.com' \
--data-binary @upload-1m-plus.bin http://127.0.0.1:18080/uploadHTTP/1.1 413 Request Entity Too Large
Date: Sat, 03 Oct 2026 19:51:56 GMT
Content-Type: text/html
Content-Length: 176
Connection: close
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx</center>
</body>
</html>The nginx rejection came from client_max_body_size, which is what proxy-body-size configures. If the product still needs that cap, set requestBuffer.limit on Envoy. Leaving the policy off keeps the larger upload that nginx used to block.
requestBuffer.limit is not connection.bufferLimit
connection.bufferLimit is the per-connection read buffer, not the maximum upload size. Changing it does not replace proxy-body-size. When request buffering is enabled, Envoy Gateway still says to size the connection buffer for those request buffers and to account for the extra memory.
requestBuffer.limit |
connection.bufferLimit |
|
|---|---|---|
| What it controls | Full request buffering and the maximum body | Per-connection incoming buffer |
| Over the setting | HTTP 413 from the request buffer | Not an HTTP body-size cap |
| This test | 1Ki allowed 1024 bytes and rejected 1025 |
1024 still allowed 2097152 bytes |
The connection buffer did not reject the upload in either check:
- The default connection buffer is 32768 bytes. The 2MiB upload above already succeeded with that default, before any request buffer existed.
- Setting
connection.bufferLimitto 1024, with the request buffer removed, still returned 200 andbytes=2097152.
A Payload Too Large response in this test came from requestBuffer.limit.
Troubleshooting Envoy Gateway 413
| Symptom | Likely cause | What to check |
|---|---|---|
413 Payload Too Large after requestBuffer is set |
The body is over requestBuffer.limit |
Raise the limit, or delete the policy if you do not want a cap |
nginx 413 Request Entity Too Large |
ingress-nginx client_max_body_size |
Check proxy-body-size. The default here was 1m |
| The upload works on Envoy and fails on nginx | The old nginx cap was not copied | Add requestBuffer.limit if the cap must remain |
Changing connection.bufferLimit does not change the maximum upload |
That field is the connection buffer | Set requestBuffer.limit for a body cap |
| HTTP 408 or 504, not 413 | A 504 is a response deadline. A 408 while the body is still uploading is the receive timeout | Troubleshoot that clock. It is not a body-size rejection |
Summary
proxy-body-size does not move onto an HTTPRoute. ingress-nginx uses it as client_max_body_size. Envoy Gateway v1.9 enforces a maximum body with BackendTrafficPolicy requestBuffer.limit, and turning that field on buffers the whole request before the application sees it. Use it for a size cap. Leave it off for WebSocket and gRPC streaming.
10m on the annotation and 10Mi on the policy are the same byte count to migrate. A 1Ki check allowed 1024 bytes and returned HTTP 413 for 1025. With no request buffer, a 2097152 byte upload returned 200, while ingress-nginx's default 1m rejected 1048577 bytes.
connection.bufferLimit did not become the upload cap. A value of 1024 still accepted the 2MiB body. Size that buffer when request buffering is on. It does not replace proxy-body-size.
References
- Envoy Gateway request buffering (v1.9)
- Envoy Gateway ClientTrafficPolicy (v1.9)
- ingress-nginx custom max body size
Frequently Asked Questions
What replaces proxy-body-size in Envoy Gateway?
BackendTrafficPolicy requestBuffer.limit. If you want to preserve the same numeric byte limit, ingress-nginx 10m corresponds to Envoy Gateway 10Mi. The mechanisms are different: the Envoy setting also enables full request buffering. Copying the annotation onto an HTTPRoute does nothing, because Envoy Gateway does not read ingress-nginx annotations.
Why did an upload start working after moving from ingress-nginx to Envoy Gateway?
The nginx cap did not come with the route. With no request buffer, a 2097152 byte POST returned HTTP 200. ingress-nginx's default client_max_body_size of 1m allowed 1048576 bytes and returned HTTP 413 for 1048577. Add requestBuffer.limit if that cap still has to exist.
Does requestBuffer.limit return HTTP 413?
Yes, when the body is larger than limit. With limit: 1Ki, 1024 bytes returned HTTP 200 and bytes=1024. 1025 bytes returned HTTP 413 and the plain text Payload Too Large. The application did not see the rejected body.
Is connection.bufferLimit the same as the request body limit?
No. It is the per-connection buffer. The default is 32768 bytes, and a 2097152 byte upload still returned 200. Setting it to 1024, with no request buffer, still returned 200 and bytes=2097152. Envoy Gateway does say to size this buffer when request buffering is enabled. That is memory for the buffer, not the replacement for proxy-body-size.
Can I use request buffering with WebSocket or gRPC streaming?
No for streaming use cases. requestBuffer waits for the complete body before forwarding it, so it is not appropriate for protocols or requests designed to remain open, including WebSocket and gRPC streaming.

