Distributed systems

413 Payload Too Large — identify which layer rejected the body

Written and reviewed by Sahil Srivastav

HTTPUploadsRequest limits
413 Payload Too Large

What this error actually means

A receiver refuses the request because its content exceeds a limit it is willing to process. The familiar reason phrase is Payload Too Large; newer HTTP semantics call status 413 Content Too Large, and older servers may display Request Entity Too Large. The numeric status is the stable identifier across those implementations.

Every body-processing hop can impose its own ceiling: edge provider, ingress, reverse proxy, framework parser, multipart parser and business validation. Raising the application setting does nothing when nginx rejects the request before forwarding it. Raising nginx does nothing when a JSON parser later buffers the whole body and rejects or exhausts memory.

The file’s size is not necessarily the request’s size. Multipart encoding adds boundaries and fields; base64 representation grows binary content; JSON adds structure; compression creates a separate distinction between bytes transferred and bytes expanded. Define which quantity the product contract limits before editing an arbitrary number.

Causes, most common first

  1. 1The first proxy’s body limit is lower than the application contract. A default or inherited limit remains on the upload route. Multiple ingress definitions or location blocks make it easy to edit a setting that never applies to the actual request path.
  2. 2The framework parser rejects before the route handler. JSON, form and multipart middleware have independent limits. A handler-level size check runs too late if middleware already attempted to buffer and parse the entire body.
  3. 3Encoding or multipart fields exceed the assumed size. The UI checks the selected file but the request includes extra form parts or an expanded text encoding. Near-threshold failures are especially easy to misread when one layer uses decimal units and another configuration uses binary-sized units.
  4. 4A compressed request expands beyond a safe budget. The transferred body is small but decompression or parsing creates a much larger representation. A limit on wire bytes alone does not bound CPU or memory consumption from hostile or accidental expansion.

When you see it

  • Uploads fail above a repeatable size while smaller requests to the same route succeed.
  • There is a proxy access-log entry but no application request entry for the rejected upload.
  • A raw binary upload succeeds while its base64-in-JSON equivalent is rejected.
  • Only one location, ingress or deployment environment fails despite identical application code.

How to diagnose it

Step 1

Measure the actual input and request representation

Start with a local, non-sensitive fixture. For multipart uploads, the wire body exceeds this file size; for JSON, measure the complete serialised JSON. Avoid comparing the displayed browser filename size with a different representation sent by the client.

wc -c ./upload.bin

Step 2

Capture the response before following redirects

Use a disposable upload destination and replace the example URL. Preserve headers and inspect the response body for the rejecting layer. curl’s Expect handling may let a server reject from Content-Length before the client sends the whole body.

curl -sv --max-time 60 -H 'Content-Type: application/octet-stream' --data-binary @./upload.bin https://api.example.com/uploads/test

Step 3

Inspect the matching nginx location and inherited limit

Read the full enclosing server and location blocks, not only the matching directive line. The effective value comes from the request’s actual configuration context. Pair it with error logs and application traces to establish whether the body reached the parser.

nginx -T

Step 4

Find the threshold in a controlled environment

Try fixtures just below and above the expected ceiling using the same encoding as production. If direct-origin requests pass and public requests fail, compare edge and ingress limits. Do not generate progressively huge production uploads merely to discover a cap.

The fix

Choose a product limit and express it at every relevant boundary. Keep the edge cap large enough for permitted multipart overhead while enforcing the business file limit inside the upload workflow. Configure only the endpoint that needs the larger allowance; a global unlimited body setting makes every route an expensive buffering target.

Stream large bodies to bounded storage with backpressure instead of collecting them in memory. Apply cumulative byte limits during reads even when Content-Length is absent or inaccurate. Count decompressed bytes where compression is accepted, and bound multipart part count and field sizes separately.

For large objects, use an authorised direct-to-object-storage or resumable upload flow with short-lived scoped permissions. Verify the final object’s size, type and integrity before exposing it to the application. Moving bytes outside the API process does not remove the need for validation and cleanup of abandoned uploads.

Return an actionable error that states the supported limit and representation. A client cannot repair a deterministic oversized body by retrying it unchanged. If rejection is temporary, Retry-After can communicate when to try again; otherwise split, compress appropriately, or use the supported upload workflow.

# Example inside the existing nginx server block; validate with nginx -t.
location /uploads/ {
    client_max_body_size 20m;
    proxy_pass http://127.0.0.1:8080;
}
# The application still validates file bytes, parser limits and storage quota.

How to stop it coming back

  • Keep boundary tests for the complete encoded request, including multipart metadata and optional fields.
  • Track 413 responses by rejecting layer so support can distinguish a product cap from accidental proxy drift.
  • Load-test permitted uploads concurrently and measure memory, temporary-disk usage and slow-client behaviour.

Practise production debugging in a real repository

Reading about a failure and reproducing one are different skills. Gronex ships broken backend repositories with failing test suites that encode the real invariant, so you debug from evidence instead of memorising symptoms.

FAQ

Should client_max_body_size be set to 0?

That disables this nginx size check. Use it only when another deliberately engineered boundary enforces the complete resource contract. A bounded route-specific limit is usually easier to operate and protects against accidental large bodies.

Will chunked transfer bypass the limit?

It should not. A receiver can count bytes as they arrive and reject once the limit is exceeded. The absence of Content-Length changes when the rejection can happen, not the endpoint’s permitted content size.

Why does the browser call this a CORS error?

A proxy-generated 413 may lack the CORS headers the application normally adds. Inspect the Network panel and proxy logs to find the underlying response. Fixing CORS alone does not make the body acceptable.

Related

Other errors engineers hit next to this one

Full error and symptom index →