413 Request Entity Too Large (tested)

413 means the server refused the request because the body was bigger than it allows. The status was called Request Entity Too Large, then Payload Too Large (Node.js still sends that text), and is Content Too Large in RFC 9110. The limit is never in the client. It is set by whatever reads the body first: a body parser, a proxy, or the language runtime. We measured where Node.js and Express draw the line and what the client sees.

1. Plain Node.js has no body limit

An http.createServer handler that just counted bytes accepted a 200 MB POST (209,715,200 bytes) with 200 in 0.18 s on localhost. Node.js limits header size, not body size. If a Node app returns 413, a framework or middleware sent it. For the header-size equivalent see Request Header Or Cookie Too Large.

2. Express: the 100kb default

express.json(), express.urlencoded(), express.raw() and express.text() all default to limit: '100kb', and kb means 1,024 bytes. Results with curl and --data-binary:

ParserBody sizeStatus
express.json()101,376 bytes (99 KB)200
express.json()102,400 bytes (exactly 100 KB)200
express.json()102,401 bytes413
express.json()103,424 bytes (101 KB)413
express.urlencoded()103,424 bytes413
express.raw()102,400 / 103,424 bytes200 / 413
express.text()103,424 bytes413
no parser on the route103,424 bytes200
express.json({ limit: '10mb' })103,424 bytes200
express.json({ limit: '10mb' })10,485,760 / 10,485,761 bytes200 / 413

Two cases that surprised us:

A body with the wrong Content-Type (for example text/plain sent to express.json()) is not checked for size at all. The parser skips it and req.body is undefined in Express 5, which in our route caused a 500. If a large upload "works" but the data is missing, check the header: Content-Type: application/json.

3. What the 413 response looks like

HTTP/1.1 413 Payload Too Large
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 994

The default error handler returns HTML. With NODE_ENV=production the body is just <pre>Payload Too Large</pre>. Otherwise it includes the stack trace, starting with PayloadTooLargeError: request entity too large at readStream (.../raw-body/index.js). The error object passed to your error middleware had these fields:

{ "name": "PayloadTooLargeError", "type": "entity.too.large",
  "status": 413, "limit": 102400, "length": 103424, "expected": 103424,
  "message": "request entity too large" }

Check err.type === 'entity.too.large' to return a JSON error your client can show:

app.use(express.json({ limit: '10mb' }));

app.use((err, req, res, next) => {
  if (err.type === 'entity.too.large') {
    return res.status(413).json({ error: 'Body too large', limit: err.limit });
  }
  next(err);
});

Raise the limit only on the routes that need it, for example app.post('/upload', express.json({ limit: '10mb' }), handler). For real file uploads, use streaming multipart handling instead of a large JSON limit.

4. In the browser: 413 can show up as a CORS error

We sent a 103,424-byte JSON body with fetch from a page on localhost:8983 to an Express API on localhost:8984, in Chromium 153:

Where the API sets Access-Control-Allow-OriginSmall body103,424-byte body
Only in the route handler (after express.json())200TypeError: Failed to fetch; the console says "blocked by CORS policy: No 'Access-Control-Allow-Origin' header"
In middleware that runs before express.json()200res.status 413, statusText "Payload Too Large", JSON body readable

The parser rejects the request before your handler runs, so headers that your handler would have set are never sent. Put the CORS middleware (for example app.use(cors())) before the body parsers. Then the frontend gets a real 413 it can handle, instead of a network error.

5. Other servers (from their docs, not measured here)

6. Checklist

  1. Find who sent the 413. An Express HTML body with Content-Security-Policy: default-src 'none' comes from Express. A different error page (for example one naming nginx) means a proxy in front rejected it.
  2. Measure the real body size in bytes, after decompression.
  3. Raise the limit on the layer that rejected it: express.json({ limit: '10mb' }), nginx client_max_body_size, etc.
  4. Return a JSON 413 with CORS headers so the browser can read it.

Frequently asked questions

What is Express's default body size limit?

100kb, which is 102,400 bytes. In Express 5.2.1 express.json() accepted exactly 102,400 bytes and returned 413 at 102,401. express.urlencoded(), express.raw() and express.text() have the same default.

How do I fix PayloadTooLargeError: request entity too large?

Pass a larger limit to the parser that rejected the body, for example express.json({ limit: '10mb' }), and if a proxy like nginx sits in front, raise its client_max_body_size too. With limit '10mb', 10,485,760 bytes passed and 10,485,761 got 413.

Does Node.js have a request body size limit?

No. A plain http.createServer handler on Node.js 24.21.0 accepted a 200 MB body. A 413 from a Node app comes from a framework, middleware or proxy.

Does gzip compression get around the 413 limit?

No. A 103,424-byte JSON body gzipped to 155 bytes still got 413 from express.json(), because the limit is applied to the decompressed size.

Why does fetch say 'Failed to fetch' instead of 413?

The 413 response had no Access-Control-Allow-Origin header, because the body parser rejected the request before the handler that sets it ran. Chromium 153 reported a CORS error. Moving CORS middleware before express.json() made the 413 readable.

Written with AI assistance. Measured on 2026-10-02 with Node.js 24.21.0 (plain http.createServer on localhost:8983), Express 5.2.1 with body-parser 2.3.0 (localhost:8984, NODE_ENV development and production), curl 8.14.1 as the client, and headless Chromium 153.0.8010.12 (Playwright) for the browser fetch test. Request bodies were exact byte counts generated with Python 3.13.5. Flask could not be installed here (no pip) and nginx and PHP are not installed, so their settings are quoted from their documentation, not run. PlainHeaders is our extension; Node.js, Express, curl and Chromium are free.