Image uploads over 1 MB fail with a bare 413 #3

Closed
opened 2026-09-11 22:57:44 +00:00 by iec-admin · 0 comments
Contributor

Symptom

Uploading an image in the admin fails with Failed to load resource: the server responded with a status of 413. No usable error appears in the UI.

Cause

Not application code. The platform ingress (nginx) caps request bodies at 1 MB — its default proxy-body-size — and rejects the request before it reaches Next.js. The app was configured to accept 10 MB images / 15 MB video, so those limits could never actually be honoured.

Probed against production:

Body size Result
0.5 MB 401 — reached the app (auth rejection)
1 MB 413 — killed at the ingress
2 MB 413
5 MB 413

Because nginx rejects it, no server handler runs and there is no JSON body to render a friendly message from.

Fix

Re-encode photos in the browser before upload, which is the only place the limit can be honoured without a platform change. Images are downscaled to a 2560px longest edge and re-encoded (WebP, JPEG fallback) under a 900 KB budget, leaving headroom for multipart overhead.

This is worth doing regardless: every image is served through next/image, which re-encodes on demand anyway, so storing untouched multi-megabyte originals only cost database space and hero re-encode time on a small pod.

Known limitation

Video and animated GIF cannot be re-encoded in the browser, so they are still bound by the 1 MB ingress cap — they now fail fast with a clear explanation instead of a raw 413. Raising nginx.ingress.kubernetes.io/proxy-body-size on the ingress is the real fix for those; there is no catalog-info field exposing it today.

## Symptom Uploading an image in the admin fails with `Failed to load resource: the server responded with a status of 413`. No usable error appears in the UI. ## Cause Not application code. The platform ingress (nginx) caps request bodies at **1 MB** — its default `proxy-body-size` — and rejects the request before it reaches Next.js. The app was configured to accept 10 MB images / 15 MB video, so those limits could never actually be honoured. Probed against production: | Body size | Result | |---|---| | 0.5 MB | `401` — reached the app (auth rejection) | | 1 MB | `413` — killed at the ingress | | 2 MB | `413` | | 5 MB | `413` | Because nginx rejects it, no server handler runs and there is no JSON body to render a friendly message from. ## Fix Re-encode photos in the browser before upload, which is the only place the limit can be honoured without a platform change. Images are downscaled to a 2560px longest edge and re-encoded (WebP, JPEG fallback) under a 900 KB budget, leaving headroom for multipart overhead. This is worth doing regardless: every image is served through `next/image`, which re-encodes on demand anyway, so storing untouched multi-megabyte originals only cost database space and hero re-encode time on a small pod. ## Known limitation Video and animated GIF cannot be re-encoded in the browser, so they are still bound by the 1 MB ingress cap — they now fail fast with a clear explanation instead of a raw 413. Raising `nginx.ingress.kubernetes.io/proxy-body-size` on the ingress is the real fix for those; there is no catalog-info field exposing it today.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
martin/ballantyne-www#3
No description provided.