Migrate ingress-nginx proxy-body-size to Envoy Gateway

Tested on RHEL 10.2 (Coughlan)
Package kubectl v1.37.0
Envoy Gateway v1.9.2
ingress-nginx controller v1.15.1
Gateway 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
Short on time?Beta Get the gist of this 10-minute read in seconds, then ask follow-up questions

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-size to 1m, which configures NGINX client_max_body_size.
  • Envoy Gateway does not add requestBuffer to an HTTPRoute unless you configure it.
  • With no such policy, the tested route accepted a 2097152 byte upload that ingress-nginx's default 1m limit 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-size sets NGINX client_max_body_size.
  • Envoy Gateway's requestBuffer turns 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:

yaml
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

IMPORTANT
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-Length header. A chunked body of 1024 bytes against a 1Ki limit returned 200, and 1025 bytes returned Payload 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:

yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: request-buffer
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: web
  requestBuffer:
    limit: 10Mi

Two fields do the work:

  • targetRefs chooses the route. A Gateway target instead applies to every route on that Gateway.
  • requestBuffer.limit is 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:

bash
kubectl get backendtrafficpolicy request-buffer -o jsonpath=\
'{range .status.ancestors[*].conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
output
Accepted=True Accepted

Accepted=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:

bash
curl -sS -D - --max-time 15 -H 'Host: app.example.com' \
--data-binary @upload-1024.bin http://127.0.0.1:19080/upload
output
HTTP/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=1024

server: BaseHTTP/0.6 Python/3.12.15 means the application answered. The next body is 1025 bytes:

bash
curl -sS -D - --max-time 15 -H 'Host: app.example.com' \
--data-binary @upload-1025.bin http://127.0.0.1:19080/upload
output
HTTP/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 Large

Read three things in that response:

  • HTTP 413 is the body-size rejection.
  • Plain text Payload Too Large came from Envoy's request buffer in this test. Treat it as the result here, not as a rule for every 413.
  • No application server line 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:

yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: request-buffer
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: eg
  requestBuffer:
    limit: 10Mi

A 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:

bash
curl -sS -D - --max-time 30 -H 'Host: app.example.com' \
--data-binary @upload-2m.bin http://127.0.0.1:19080/upload
output
HTTP/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=2097152

That 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:

bash
curl -sS -D - --max-time 30 -H 'Host: app.example.com' \
--data-binary @upload-1m-plus.bin http://127.0.0.1:18080/upload
output
HTTP/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

NOTE
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.bufferLimit to 1024, with the request buffer removed, still returned 200 and bytes=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


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.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)