413 Content Too Large
Client error response defined by RFC 9110 §15.5.14.
Last updated July 27, 2026
Common causes at a glance
- nginx client_max_body_size (default 1m) rejecting the upload at the edge
- An API gateway or serverless platform hard cap on request payload size
- A framework body-parser limit such as Express json({ limit }) or a Django setting
- Base64 encoding inflating a binary attachment by roughly a third past the limit
Reproduce it
curl -i -X POST --data-binary @big.bin https://api.example.com/v1/uploadWhat 413 Content Too Large means
The most useful thing to know about 413 is that your application probably never saw the request. Body size limits live in the edge: nginx, an ALB, an API gateway, a WAF, or the framework’s own body parser. Whichever is strictest rejects the upload first, which is why the response often carries generic HTML rather than your API’s JSON error shape.
That layering is also why the fix is rarely in application code. Raising a limit in the framework does nothing if the reverse proxy in front of it caps requests at 1 MB. Find every hop with an opinion about body size and raise all of them, or stop sending large bodies through the request path at all.
What the spec says
RFC 9110 §15.5.14 allows the server to terminate the
request if the protocol version permits, or otherwise close the connection — which is
why a large upload sometimes dies as a broken pipe instead of a clean status. If the
limit is temporary, the spec says the server should include
Retry-After; almost nothing does, because size
caps are rarely temporary. Note the naming history: the number is 413 in every version,
but the registered phrase changed with RFC 9110, so a log line reading “413 Payload Too
Large” is an older stack, not a different condition.
What actually causes it
nginx is the usual culprit and its default is low:
client_max_body_size
is 1 MB unless you changed it.
Managed platforms are stricter and often not configurable, so a request that works
against a local dev server fails the moment it goes through the gateway. The sneaky
variant is encoding overhead: a 9 MB file base64-encoded into a JSON field becomes
about 12 MB on the wire and crosses a 10 MB cap the raw file would clear.
How to debug it
Find out which hop rejected you by reading the response body and server
header, not just the status. An nginx-generated 413 has a distinctive
<center>nginx</center> page; a gateway’s looks like the
platform’s own error JSON.
head -c 5000000 /dev/urandom > 5mb.bin
curl -i -X POST --data-binary @5mb.bin https://api.example.com/v1/upload
Bisect by size at 1 MB, 5 MB, and 20 MB; the boundary usually lands on a recognisable default. Then repeat directly against the origin to confirm which layer owns the limit. For anything genuinely large, raising caps is the wrong direction: issue a presigned upload URL so the bytes go straight to object storage and never traverse the proxy chain. Use curl-build to reproduce the exact multipart request your client sends, since browser form encoding and a hand-written command rarely produce the same byte count.
Working examples
curl
# Reproduce with a known-size body and read the error page, not just the code.
head -c 5000000 /dev/urandom > 5mb.bin
curl -i -X POST \
-H 'Content-Type: application/octet-stream' \
--data-binary @5mb.bin \
https://api.example.com/v1/upload
# Multipart behaves differently from a raw body; test the shape you actually send.
curl -i -F 'file=@5mb.bin' https://api.example.com/v1/upload
Python (requests)
import os
import requests
path = "5mb.bin"
size = os.path.getsize(path)
with open(path, "rb") as fh:
resp = requests.post(
"https://api.example.com/v1/upload",
files={"file": (path, fh, "application/octet-stream")},
timeout=120,
)
print(size, resp.status_code)
if resp.status_code == 413:
# The proxy answered, so this is usually HTML rather than your API's JSON.
print(resp.headers.get("Server"), resp.text[:200])
Node (fetch)
import { readFile } from "node:fs/promises";
const bytes = await readFile("5mb.bin");
const form = new FormData();
form.append("file", new Blob([bytes]), "5mb.bin");
const res = await fetch("https://api.example.com/v1/upload", {
method: "POST",
body: form,
});
console.log(bytes.byteLength, res.status, res.headers.get("server"));
if (res.status === 413) console.log((await res.text()).slice(0, 200));Reference and tooling
The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.
Other 4xx codes
- 400 Bad Request
- 401 Unauthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 411 Length Required
- 412 Precondition Failed
- 414 URI Too Long
- 415 Unsupported Media Type
- 416 Range Not Satisfiable
- 417 Expectation Failed
- 418 (Unused)
- 419 Page Expired
- 421 Misdirected Request
- 422 Unprocessable Content
- 423 Locked
- 424 Failed Dependency
- 425 Too Early
- 426 Upgrade Required
- 428 Precondition Required
- 429 Too Many Requests
- 431 Request Header Fields Too Large
- 444 No Response
- 451 Unavailable For Legal Reasons
- 494 Request Header Too Large
- 495 SSL Certificate Error
- 496 SSL Certificate Required
- 497 HTTP Request Sent to HTTPS Port
- 499 Client Closed Request