Skip to content
Control Plane Labs

413 Content Too Large

Client error response defined by RFC 9110 §15.5.14.

Written and maintained by Ben Ennis

Last reviewed July 27, 2026 · How we verify this

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/upload

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

The one 413 limit you cannot configure away: API Gateway’s 10 MB ceiling

Every cause above assumes raising a limit somewhere fixes the problem. AWS API Gateway breaks that assumption: its REST API quotas table lists a 10 MB payload size limit explicitly marked as not adjustable, unlike almost every other API Gateway quota, which can be raised through a service quota increase request. There is no configuration setting, execution-role permission, or support ticket that moves this number; a request body over 10 MB is rejected at the gateway before your integration, Lambda function, or backend ever runs.

This makes API Gateway’s 413 fundamentally different from nginx’s client_max_body_size covered above: one is a default you change, the other is a platform ceiling you design around. If a service in front of API Gateway needs to accept anything close to 10 MB, especially after base64 inflation on a binary payload, the standard workaround is to skip the request body entirely — issue a presigned S3 URL and have the client upload directly to object storage, then notify your API of the object key once the upload completes. Lambda function URLs and Application Load Balancer targets have their own separate payload ceilings, so migrating off API Gateway does not automatically remove the constraint; check the specific compute target’s documented limit rather than assuming the 10 MB number travels with it.

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