How HTTP Works

Debug guide · you're seeing

  • 413 Request Entity Too Large
  • nginx/1.27.0 (413 Request Entity Too Large)
  • [error] 29#29: *1 client intended to send too large body: 5242880 bytes, client: 203.0.113.10, server: example.com, request: "POST /upload HTTP/1.1"
  • PayloadTooLargeError: request entity too large

nginx 413 Request Entity Too Large: client_max_body_size

Fix nginx 413 Request Entity Too Large: raise client_max_body_size, then check Cloudflare limits, ingress-nginx proxy-body-size and PHP upload limits.

Reviewed 5 min readbeginner5 sourcesTry itMarkdown
On this page

TL;DR: The request body is larger than nginx’s client_max_body_size, which defaults to 1m. Raise it in the http, server or location block that serves the endpoint, run nginx -t && nginx -s reload, then check the other layers that cap uploads: ingress-nginx (proxy-body-size, default 1m), Cloudflare (100 MB on Free and Pro), PHP and your framework.

What it means

nginx compares the request’s Content-Length with client_max_body_size as soon as it has read the request headers. If the declared length is larger, it answers 413 without reading the body and logs a line in the error log. The nginx docs themselves warn that “browsers cannot correctly display this error”. For chunked uploads, which have no Content-Length, nginx counts bytes as the body arrives and fails when the limit is crossed.

The browser-visible page and the log line:

HTTP/1.1 413 Request Entity Too Large
Server: nginx/1.27.0
Content-Type: text/html
Connection: close
2026/10/04 09:12:41 [error] 29#29: *1 client intended to send too large body: 5242880 bytes, client: 203.0.113.10, server: example.com, request: "POST /upload HTTP/1.1", host: "example.com"

The line names the number of bytes the client announced. That number tells you how far to raise the limit and, if it is much smaller than the limit you configured, which other layer is responsible.

Who sent it?

Several layers can return a 413 and they look alike in the browser:

  • nginx on your host: HTML body ending in nginx (or nginx/1.x.y), and the log line above in /var/log/nginx/error.log.
  • ingress-nginx in Kubernetes: same page, same log line, but in the ingress controller pod’s logs, not your application’s.
  • Cloudflare: the response has Server: cloudflare and a CF-Ray header, and no matching line in your origin logs, because the request never reached the origin.
  • Your application framework: a JSON or framework-styled error body, for example PayloadTooLargeError: request entity too large from Express body parsers. nginx logs an ordinary request with the status your app returned.

If your origin’s access log has no entry for the failing request, the limit is in front of it. If nginx’s error log has the too large body line, it is nginx.

Fix it, in order of likelihood

  1. Find the effective config. Edits to a file that is never included do nothing: nginx -T | grep -n client_max_body_size.
  2. Raise client_max_body_size at the narrowest context that covers the endpoint, for example only for the upload location.
  3. Reload (nginx -t && nginx -s reload) and retest with a file just above the old limit.
  4. If it still fails, remove limits further along: ingress annotation, Cloudflare plan limit, PHP and application limits below.

nginx

http {
    # Default for the whole server: 1m if unset
    client_max_body_size 1m;

    server {
        server_name example.com;

        location /upload {
            client_max_body_size 100m;
            client_body_timeout 120s;   # slow clients need more than the 60s default
            proxy_request_buffering off; # stream the body to the upstream instead of buffering to disk
            proxy_pass http://app;
        }
    }
}

A value in location replaces the inherited one, and 0 means unlimited. Keep the limit as small as the product allows; it is the cheapest protection against oversized-body abuse.

Kubernetes ingress-nginx

The controller’s default is also 1m. Set it per Ingress with an annotation:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: uploads
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
spec:
  ingressClassName: nginx
  rules:
    - host: example.com
      http:
        paths:
          - path: /upload
            pathType: Prefix
            backend:
              service:
                name: app
                port:
                  number: 80

To change it cluster-wide, set proxy-body-size in the controller’s ConfigMap. Annotation values must be quoted strings.

Cloudflare

Cloudflare limits the request body size per plan before the request reaches your origin. Per Cloudflare’s upload limits, the maximum is 100 MB on Free and Pro, 200 MB on Business, and up to 5 GB on Enterprise, which Enterprise customers can adjust themselves on the zone’s Network page. Check that page before relying on the numbers; Cloudflare has changed them before. Raising client_max_body_size cannot get past this. For larger files, upload directly to object storage with a pre-signed URL, use a chunked or resumable upload protocol so each request stays below the limit, or send the upload to a DNS-only (grey-clouded) hostname.

PHP behind nginx or Apache

PHP has its own limits and they fail differently: the request is accepted by the web server, then PHP discards the body, $_FILES is empty and $_POST is empty. There is often no 413 at all.

; php.ini
upload_max_filesize = 100M   ; per file, default 2M
post_max_size       = 105M   ; whole request body, default 8M; keep it larger than upload_max_filesize
memory_limit        = 256M       ; default 128M
max_execution_time  = 120        ; default 30 (web SAPI)

Set post_max_size at least as large as upload_max_filesize, and keep nginx’s client_max_body_size at or above post_max_size. Restart PHP-FPM after editing.

Node and Express

Body parsers have small defaults, so a 413 can come from the app:

app.use(express.json({ limit: '5mb' }))            // default is 100kb
app.use(express.urlencoded({ extended: true, limit: '5mb' }))

For multipart uploads, set the limit in the parser (multer({ limits: { fileSize: 100 * 1024 * 1024 } })), not in express.json.

Other servers

Apache:       LimitRequestBody 104857600        (bytes; 0 = unlimited, which is the default)
Caddy:        request_body { max_size 100MB }
Spring Boot:  spring.servlet.multipart.max-file-size=100MB
              spring.servlet.multipart.max-request-size=105MB   (defaults 1MB and 10MB)

Reproduce and verify

Create a file just over the limit and post it. curl -v shows the response status and, if you use -w, how many bytes were sent before the server answered:

head -c 5242880 /dev/zero > test-5mb.bin

curl -sS -o /dev/null -w '%{http_code}\n' \
  -X POST --data-binary @test-5mb.bin \
  -H 'Content-Type: application/octet-stream' \
  https://example.com/upload

Before the fix you get 413; after it, whatever your endpoint returns for a valid upload (200, 201, 204, or an auth error, which proves the body size check was passed). To test the proxy without the CDN, send the same request straight to the origin with a Host header:

curl -sS -o /dev/null -w '%{http_code}\n' -X POST --data-binary @test-5mb.bin \
  -H 'Host: example.com' http://ORIGIN_IP/upload

If the origin answers 200 and the public URL answers 413, the limit is at the CDN or load balancer.

  • 413 Content Too Large covers the status itself, including when a server may close the connection.
  • 400 Bad Request is what some frameworks return for a malformed or truncated body rather than 413.
  • 504 Gateway Timeout appears next when large uploads are slow enough to trip proxy_read_timeout or client_body_timeout.
  • Content-Length is the value nginx compares against the limit.

Frequently asked questions

What is the default client_max_body_size in nginx?

The default is 1m (one megabyte). Any request whose Content-Length is larger gets a 413 before it reaches your application. Setting the value to 0 disables the check entirely, which is not recommended on a public endpoint.

Where do I put client_max_body_size?

It is valid in the http, server and location contexts. A value in location overrides server, which overrides http. A common mistake is raising it in the server block while a more specific location block, or a separate included file, still sets a smaller value.

Why does my upload fail with a network error instead of a 413 in the browser?

nginx sends the 413 and may close the connection while the client is still uploading the body. Depending on timing, the browser sees the connection reset before it reads the response, so fetch rejects with a generic network error. The nginx error log still shows "client intended to send too large body".

I raised client_max_body_size and still get 413. What else limits uploads?

Another layer is enforcing a lower limit. Check a CDN or WAF in front (Cloudflare caps uploads by plan), an ingress controller (ingress-nginx defaults to 1m), the application framework (Express body parsers default to 100kb for JSON) and the nginx config you edited versus the one actually loaded (nginx -T shows the effective configuration).

Is the status 413 Request Entity Too Large or Content Too Large?

RFC 9110 renamed it 413 Content Too Large. nginx still prints the older "Request Entity Too Large" text on its error page, and many clients display that string, so both phrases search for the same problem.

Sources

  1. nginx: ngx_http_core_module client_max_body_sizenginx.org
  2. MDN: 413 Content Too Largedeveloper.mozilla.org
  3. RFC 9110 Section 15.5.14: 413 Content Too Largerfc-editor.org
  4. ingress-nginx: Annotations, Custom max body sizekubernetes.github.io
  5. Cloudflare Docs: Upload limitsdevelopers.cloudflare.com

Keep going

Browse /search