# 413 Request Entity Too Large: Find the Layer, Fix the Limit

> 413 Request Entity Too Large comes from one of several limits: CDN, proxy, ingress or framework. How to tell which one, and when not to raise it.

1. [Home](/)
2. [Blog](/blog)
3. 413 Request Entity Too Large: Which Layer Said No, and When to Stop Raising the Limit

DeploymentOctober 2, 20269 min read

# 413 Request Entity Too Large: Which Layer Said No, and When to Stop Raising the Limit

A 413 comes from whichever size limit in the request path is smallest: the CDN, the proxy, the ingress or the framework. How to tell which one answered, how to raise it, and when a large upload shouldn't go through your app at all.

## The short version

A 413 means some layer between the client and your code refused the request body because it was bigger than that layer allows. There are usually three or four such limits in a row, and the smallest one wins, so the fix starts with finding out which layer answered. For JSON and form bodies, raise that limit. For large files, stop sending them through the app and upload them straight to object storage with a presigned URL.

[Teo Marquardt](/about#author)· Facts last checked October 1, 2026

Everything worked in testing. Then a real user uploads a 12 MB photo, or a client posts a JSON export a bit larger than the ones you tried, and the request fails with `413 Request Entity Too Large`. Your application logs are often empty, because in most cases the request never reached your code. Something in front of it read the `Content-Length`, compared it with a number in its config and refused.

The usual advice is one line: raise `client_max_body_size`. Sometimes that fixes it. Often it doesn't, because nginx was only one of several limits, and the next one down the chain answers with the same status code. This article is about finding the layer that actually said no, and about the case where raising the limit is the wrong answer.

## What "413 Request Entity Too Large" actually means

HTTP 413 means the server refuses to process a request because its body is larger than the server is willing or able to accept. The status has had three names. RFC 2616 called it `Request Entity Too Large`, RFC 7231 renamed it `413 Payload Too Large`, and [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#name-413-content-too-large), the current HTTP specification from 2022, calls it `413 Content Too Large`. The code and the meaning stayed the same. nginx and many frameworks still print the oldest name, which is why that's the phrase people search for.

The limit is about the body: the uploaded file, the JSON payload, the form data. Oversized headers or cookies are a different failure. RFC 6585 gives them their own status, `431 Request Header Fields Too Large`, and nginx answers an oversized header with a `400` instead. If you see a 413 on a request with a tiny body, check for a proxy that reports the wrong code before you go looking for size limits.

Retrying a 413 doesn't help

A 429 or a 503 can succeed on the next attempt. A 413 normally won't: the same body hits the same limit every time. The rare exception is a 413 that carries a `Retry-After` header, which signals a temporary condition. Client code that retries every 4xx with backoff should exclude 413 and surface it to the user, or switch to a different upload path. The retry logic for codes that do deserve it is in [rate limiting, 429 and Retry-After](/blog/rate-limiting-429-retry-after).

## Whose 413 is it? Read the response body

Every layer that enforces a size limit formats its rejection differently, so the response body usually names the culprit. Open the failed request in your browser's network tab or repeat it with `curl -i` and look at what came back, not just the status line.

html

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

That HTML page, with `<center>nginx</center>` at the bottom, comes from nginx itself. The ingress-nginx controller in Kubernetes produces the same page, because it is nginx underneath. A JSON body such as `{"message":"request entity too large"}` or a stack trace mentioning `PayloadTooLargeError` means the request got through every proxy and your framework rejected it. A branded error page with a ray ID or similar request identifier points at a CDN or edge proxy.

| Layer                      | Default limit (as of writing)                             | How its 413 looks                                      | Setting to change                           |
| -------------------------- | --------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------- |
| CDN or edge proxy          | Depends on plan; Cloudflare allows 100 MB on Free and Pro | Provider-branded error page                            | Plan level, often not configurable          |
| nginx                      | 1 MB                                                      | HTML page ending in <center>nginx</center>             | client\_max\_body\_size                     |
| ingress-nginx (Kubernetes) | 1 MB                                                      | Same nginx HTML page                                   | nginx.ingress.kubernetes.io/proxy-body-size |
| Apache httpd               | 1 GiB since 2.4.54                                        | Apache error page                                      | LimitRequestBody                            |
| Express express.json()     | 100 kb                                                    | PayloadTooLargeError: request entity too large         | limit option of the body parser             |
| Spring Boot multipart      | 1 MB per file, 10 MB per request                          | Framework error, often a 413 or 500 depending on setup | spring.servlet.multipart.\*                 |

Defaults from each project's documentation, as of October 2026\. Your platform may set its own values on top.

### When the 413 shows up as a CORS error

In a browser, a 413 from a proxy often doesn't look like a 413 at all. The console reports a CORS failure instead. The cause is boring: your application adds the `Access-Control-Allow-Origin` header, but the proxy rejected the request before your application saw it, so its error page has no CORS headers, and the browser hides the response from your JavaScript. If uploads above a certain size fail with a CORS error while small ones work, treat it as a size limit first. Some proxies also close the connection before reading the whole body, in which case the browser reports a reset connection instead of any status.

## The limits, from the outside in

A request is checked against every size limit on its path, and the smallest one wins. Each layer compares the body with its own number, in order, from the edge inwards. Raise them from the outside in, and remember that the inner ones only matter once the outer ones let the request through.

### CDN and edge proxies

If your domain sits behind a CDN that proxies requests, the CDN has a body size ceiling of its own. On Cloudflare it's tied to the plan, at [100 MB on Free and Pro](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/4xx-client-error/error-413/) and higher on Business and Enterprise. You usually can't raise it in config. For files that large the way around it is to not send them through the proxied domain at all, which is the subject of the section on presigned uploads below.

### nginx: client\_max\_body\_size

nginx's [client\_max\_body\_size](https://nginx.org/en/docs/http/ngx%5Fhttp%5Fcore%5Fmodule.html#client%5Fmax%5Fbody%5Fsize) defaults to `1m`, which is why a reverse proxy with an untouched config rejects the first realistic upload. The directive works in the `http`, `server` and `location` contexts. Setting it to `0` turns the check off, which is rarely what you want on a public endpoint.

nginx

```
server {
    server_name app.example.eu;

    # Applies to the whole site
    client_max_body_size 10m;

    location /api/uploads {
        # Larger limit only where uploads are expected
        client_max_body_size 50m;
        proxy_pass http://app:3000;
    }
}
```

Scoping the larger value to the upload route keeps the rest of the site protected from someone posting a gigabyte of junk to your login form.

### Kubernetes ingress: proxy-body-size

In a cluster with ingress-nginx, editing an nginx.conf won't help, because the controller generates it. The limit is set per Ingress with an annotation, and it also defaults to 1 MB.

yaml

```
metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
```

If there's also an nginx inside the application container, it keeps its own `client_max_body_size`. Two nginx instances in a row means two limits to raise, and the 413 page looks identical whichever one sent it. A reply on r/nginx sums it up: the 413 can come from the reverse proxy or from the backend, so check the config on both. In practice it's often both.

### Express and NestJS: PayloadTooLargeError

Once the proxies let the body through, the framework gets its turn. Express's built-in JSON parser accepts [100 kb by default](https://expressjs.com/en/api.html#express.json), which is far less than people expect. A rich-text document or an array of a few thousand records goes over it easily, and the log shows this:

text

```
PayloadTooLargeError: request entity too large
    at readStream (/app/node_modules/raw-body/index.js)
    at getRawBody (/app/node_modules/raw-body/index.js)
    at read (/app/node_modules/body-parser/lib/read.js:79:3)
```

javascript

```
// Raise the limit for JSON bodies only, and only as far as you need
app.use(express.json({ limit: "2mb" }));
app.use(express.urlencoded({ extended: true, limit: "2mb" }));
```

NestJS uses the same body parser under its Express adapter, so the error text is identical and the fix is the same option, passed when the app is created. File uploads in Express normally go through a multipart middleware such as multer instead, which has its own `limits.fileSize` setting, unset by default, and ignores the JSON limit.

### Django, Flask, Spring Boot and PHP

* Django limits non-file request data with `DATA_UPLOAD_MAX_MEMORY_SIZE`, 2.5 MB by default. Going over it raises `RequestDataTooBig`, which Django answers with a 400, not a 413\. If nothing in your stack sends a 413, that's why.
* Flask has no overall body limit unless you set `MAX_CONTENT_LENGTH`. Once set, oversized requests get a 413 from Werkzeug's `RequestEntityTooLarge`. Recent versions also cap in-memory form fields with `MAX_FORM_MEMORY_SIZE`.
* Spring Boot caps multipart uploads at 1 MB per file and 10 MB per request through `spring.servlet.multipart.max-file-size` and `max-request-size`.
* PHP has `upload_max_filesize` and `post_max_size`. PHP itself doesn't answer with a 413 when they're exceeded. Past `post_max_size`, `$_POST` and `$_FILES` arrive empty; past `upload_max_filesize`, the file's entry carries the error `UPLOAD_ERR_INI_SIZE`. Both are more confusing than a status code. The 413 people see on PHP sites almost always comes from the nginx in front.

## "I raised client\_max\_body\_size and still get 413"

This is the most common follow-up question, and it usually comes down to one of four things:

1. The config wasn't reloaded. Run `nginx -t` to check the syntax, then `nginx -s reload` or restart the container. A changed file on disk does nothing on its own.
2. The directive is in the wrong block. A value in one `server` block doesn't apply to another, and a more specific `location` with its own smaller value overrides the server-wide one. Search the full config, including included files, for every occurrence.
3. It's a different nginx. The one you edited may be the inner one, while an ingress, a load balancer or a platform proxy in front still has the default. The response body is identical, so check each hop.
4. It's not nginx at all. If the 413 now comes back as JSON or a framework error, nginx is fixed and the next limit is in your application.

## When raising the limit is the wrong fix

For JSON payloads and ordinary forms, raising the limit to a sensible value is the right move. A 5 MB JSON body is unusual but not dangerous. Files are different. Once uploads reach tens or hundreds of megabytes, every layer in the path has to cope with them, and raising one limit tends to expose the next problem.

* A web process receiving a 500 MB video is tied up for as long as the upload takes, which on a slow mobile connection can be many minutes.
* Proxies and load balancers have read timeouts of their own, so a large slow upload can fail with a 408 or 504 even after every size limit has been raised.
* Depending on the framework, the body is buffered in memory or written to a temporary disk on the instance, both of which are limited on a container.
* If the file ends up in object storage anyway, it crosses the network twice: from the user to your app, then from your app to the bucket.

The fix that removes the problem is to let the browser upload the file straight into the bucket. Your app signs a short-lived [presigned URL](/blog/s3-presigned-urls) for one object, hands it to the client, and the client sends the file directly to storage. Your web process only handles a small JSON request for the URL and, later, a confirmation. The size limit doesn't go away. It moves into the signature. A presigned PUT pins one exact `Content-Length`, and a presigned POST can carry a `content-length-range` condition on providers that support POST policies; whether yours does is worth checking, as the presigned URL guide explains. For files in the hundreds of megabytes and up, split the upload into parts; the details of [multipart uploads and what to retry](/blog/upload-files-to-s3) are a topic of their own.

| What's in the request body         | Typical size     | What to do about a 413                                                            |
| ---------------------------------- | ---------------- | --------------------------------------------------------------------------------- |
| JSON API payload                   | KB to a few MB   | Raise the framework's body limit to a realistic maximum, plus the proxy if needed |
| HTML form with fields              | KB               | Raise the form limit; if it's still too big, look at what the form is sending     |
| Avatar or document upload          | Up to \~10 MB    | Either path works; raising limits on one route is fine                            |
| Video, archives, datasets, backups | Tens of MB to GB | Upload directly to object storage with a presigned URL, multipart for large files |

## How Runsite handles it

On Runsite the app and the bucket live next to each other. You [deploy the web service](/services/web-services) from Git and set the body limits that belong to your application, in your framework config, the same way you would anywhere else. Most hosting platforms also put a request size limit in front of your service, so if you hit a 413 you can't explain from your own config, check your platform's documentation for that limit before raising anything else.

For anything large, [S3-compatible object storage in the EU](/services/s3-storage) works with the standard AWS SDKs, presigned URLs included, so the browser can upload straight to the bucket and your web process never carries the file. Objects can be up to 5 TB, with multipart recommended above 100 MB. Storage costs €0.025 per GB a month after the first 5 GB, with no charge for transfer in or out. The bucket is hosted in Germany, next to the services that use it.

## The short version

* `413 Request Entity Too Large` (now officially `413 Content Too Large`) means a layer refused the request body because it exceeded that layer's limit.
* There are usually several limits in a row: CDN, proxy, ingress, framework. The smallest one wins, and the response body tells you which one answered.
* nginx and ingress-nginx default to 1 MB, Express's JSON parser to 100 kb. Raise them from the outside in, and reload after every change.
* Don't retry a 413\. The same body gets the same answer.
* For large files, stop raising limits and upload directly to object storage with a presigned URL.

Related service

## Web Services on Runsite

Deploy from a single git push and keep active apps warm — no cold starts, hosted entirely in the EU with a signed GDPR DPA on every plan.

[Explore Web Services](/services/web-services)

[Back to all articles](/blog)

FAQ

## Frequently Asked Questions

Common questions about this service.

### How can I fix the HTTP 413 error?

First find out which layer sent it. An HTML page ending in "nginx" means nginx or ingress-nginx, where you raise client\_max\_body\_size or the proxy-body-size annotation and reload. A JSON error or PayloadTooLargeError means your framework, where you raise the body parser's limit. A branded error page means a CDN, whose limit is usually tied to your plan. Raise each limit only as far as your real maximum request needs. If the bodies are large files, upload them directly to object storage with a presigned URL instead of raising limits further.

### What does the 413 error code mean?

HTTP 413 means the server refused to process the request because the request body is larger than it is willing or able to accept. RFC 9110 names it 413 Content Too Large; older specifications called it Payload Too Large and Request Entity Too Large, which is the text nginx still shows. It is a client error: unless the response carries a Retry-After header, sending the same request again gives the same result, so the body has to get smaller, the server's limit has to go up, or the data has to take a different route such as a direct upload to storage.

### What is meant by request entity too large?

"Request entity" is the old HTTP term for the body of a request, such as an uploaded file, a JSON payload or submitted form data. "Request entity too large" means that body was bigger than a limit configured somewhere between the client and the application, for example nginx's client\_max\_body\_size, which defaults to 1 MB, or Express's JSON parser limit, which defaults to 100 kb. It says nothing about headers or URLs, which have their own limits and status codes.

### What does the API error 413 mean?

In an API, a 413 means the JSON or file you sent exceeded the size the API or its proxy accepts. With a Node.js backend the log usually shows PayloadTooLargeError: request entity too large, because Express's JSON parser accepts only 100 kb by default. If you own the API, raise the parser limit to your realistic maximum, and the proxy limit if one sits in front. If you're calling someone else's API, check its documented size limit and split the payload into smaller requests or use its file-upload endpoint.

Keep reading

## Related articles

[Storage13 min readPresigned URLs: Share and Upload Private S3 Files Without Handing Out Your KeysA presigned URL isn't a token the storage issued you. It's a signature you computed yourself, and almost everything people get wrong about expiry, revocation, and safety follows from that. Here's the mechanism, what one looks like, and how to use it for both downloads and uploads.Jul 31, 2026Read](/blog/s3-presigned-urls)[Storage11 min readUploading User Files to S3: Keys, Multipart, and What to RetryUploading a file is three lines of SDK code, and they stay correct until a real user shows up with a 3 GB video on hotel wifi. Here's what comes after the tutorial: naming objects you can't rename, splitting large files, and knowing which failures are safe to repeat.Aug 3, 2026Read](/blog/upload-files-to-s3)[Caching13 min read429 Too Many Requests: Rate Limiting from Both SidesYou are usually on both ends of this at once — limited by somebody's API and limiting your own. One header joins the two sides, and almost nobody sends or reads it properly.Aug 28, 2026Read](/blog/rate-limiting-429-retry-after)

## Your app deserves to be online

€5 of credit on signup. Deploy in under a minute. No credit card needed.

[Start deploying](https://dashboard.runsite.app/login)[View documentation](https://docs.runsite.app)

---

Source: https://runsite.app/blog/413-request-entity-too-large
