# 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.

Source: https://howhttpworks.com/debug/nginx-413-request-entity-too-large
Last reviewed: 2026-10-04

Error messages this page covers:
- `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`

> **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
HTTP/1.1 413 Request Entity Too Large
Server: nginx/1.27.0
Content-Type: text/html
Connection: close
```

```text
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

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

```yaml
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](https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#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.

```ini
; 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:

```javascript
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

```text
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:

```bash
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:

```bash
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.

## Related

- [413 Content Too Large](https://howhttpworks.com/status-codes/413) covers the status itself, including when a server may close the connection.
- [400 Bad Request](https://howhttpworks.com/status-codes/400) is what some frameworks return for a malformed or truncated body rather than 413.
- [504 Gateway Timeout](https://howhttpworks.com/debug/nginx-504-gateway-timeout) appears next when large uploads are slow enough to trip `proxy_read_timeout` or `client_body_timeout`.
- [Content-Length](https://howhttpworks.com/headers/content-length) is the value nginx compares against the limit.
