nginx refuses every upload over 1 MB: maps, handout files, larger pictures and campaign imports fail in a deployed build #1057

Closed
opened 2026-10-04 00:00:52 +00:00 by claude-bot · 2 comments
Collaborator

Found while planning #991 part 2, and confirmed on dev (v4.5.0-rc2).

Seen

webapp/frontend/nginx.conf sets no client_max_body_size, so nginx uses its default of 1 MB for everything it proxies to the backend. On dev, through the frontend's own port:

2 MB POST to /api/campaigns/import   -> 413   (nginx; the backend never sees it)
0.5 MB POST to the same route        -> 401   (reached the backend: not signed in)

nginx -T in the running frontend container shows no client_max_body_size at all. The v4.4.1 tag's nginx.conf has none either, so production behaves the same.

What it breaks

Every upload the backend allows above 1 MB:

  • map images (map_max_upload_bytes, 75 MB)
  • handout files and page pictures (media_max_upload_bytes, 8 MB)
  • a campaign import (100 MB today; 1 GB after D273)
  • larger content packs

The person gets nginx's bare 413 page, not the app's {"detail": ...}.

Tests miss it because the backend suite calls the app directly, and the journeys upload small files.

Fix

Set client_max_body_size on the /api/ location to just above the largest upload the backend accepts, and leave the per-kind limits to the backend, which already enforces them with a proper message. Add a test that reads nginx.conf and fails if the proxy's limit falls below the backend's largest (the same kind of check as the /media/private one).

#991 part 2 will give the import route its own location, with the backend's admin-settable limit as the only one.

Found while planning #991 part 2, and confirmed on dev (`v4.5.0-rc2`). ## Seen `webapp/frontend/nginx.conf` sets no `client_max_body_size`, so nginx uses its default of 1 MB for everything it proxies to the backend. On dev, through the frontend's own port: ``` 2 MB POST to /api/campaigns/import -> 413 (nginx; the backend never sees it) 0.5 MB POST to the same route -> 401 (reached the backend: not signed in) ``` `nginx -T` in the running frontend container shows no `client_max_body_size` at all. The `v4.4.1` tag's `nginx.conf` has none either, so production behaves the same. ## What it breaks Every upload the backend allows above 1 MB: - map images (`map_max_upload_bytes`, 75 MB) - handout files and page pictures (`media_max_upload_bytes`, 8 MB) - a campaign import (100 MB today; 1 GB after D273) - larger content packs The person gets nginx's bare 413 page, not the app's `{"detail": ...}`. Tests miss it because the backend suite calls the app directly, and the journeys upload small files. ## Fix Set `client_max_body_size` on the `/api/` location to just above the largest upload the backend accepts, and leave the per-kind limits to the backend, which already enforces them with a proper message. Add a test that reads `nginx.conf` and fails if the proxy's limit falls below the backend's largest (the same kind of check as the `/media/private` one). #991 part 2 will give the import route its own location, with the backend's admin-settable limit as the only one.
Author
Collaborator

Fixed in PR #1058, merged to main on 2026-10-04 with CI green.

  • The fix. location /api/ now sets client_max_body_size 110m. tests/test_nginx_upload_limit.py fails if that is ever missing or falls below the backend's largest upload.
  • Checked so far. The test passes and nginx -t accepts the config.
  • Still to see. A real upload over 1 MB going through a running build. That is the first check on dev after the next prerelease (v4.5.0-rc3); I will report it here.
  • Production. Prod runs the 4.4.1 images, whose nginx has the same 1 MB default, so prod has this bug until 4.5.0 ships. On 4.4.1 that means page pictures and campaign imports over 1 MB; maps and handout files are new in 4.5.0. If that is hurting anyone before 4.5.0, a 4.4.2 with only this change is possible. That is the owner's call.
Fixed in PR #1058, merged to main on 2026-10-04 with CI green. - **The fix.** `location /api/` now sets `client_max_body_size 110m`. `tests/test_nginx_upload_limit.py` fails if that is ever missing or falls below the backend's largest upload. - **Checked so far.** The test passes and `nginx -t` accepts the config. - **Still to see.** A real upload over 1 MB going through a running build. That is the first check on dev after the next prerelease (`v4.5.0-rc3`); I will report it here. - **Production.** Prod runs the 4.4.1 images, whose nginx has the same 1 MB default, so prod has this bug until 4.5.0 ships. On 4.4.1 that means page pictures and campaign imports over 1 MB; maps and handout files are new in 4.5.0. If that is hurting anyone before 4.5.0, a 4.4.2 with only this change is possible. That is the owner's call.
Author
Collaborator

On dev as v4.5.0-rc3, checked at nginx on 2026-10-04.

Request to dev's nginx (no session) Answer Meaning
4 MB body to an /api/ route JSON from the backend nginx let it through; the old 1 MB default would have answered 413
120 MB body to the same route nginx's own 413 the 110m limit holds
120 MB body to /api/campaigns/import 401 {"detail":"Not authenticated"} from the backend the import route has no nginx limit, and the backend refuses before reading the body

Not done: a signed-in upload on dev. Dev needs a real login, which I do not have. A signed-in 4.1 MB upload was confirmed earlier in a seeded stack built from main.

Production keeps the 1 MB default until 4.5.0.

**On dev as `v4.5.0-rc3`, checked at nginx on 2026-10-04.** | Request to dev's nginx (no session) | Answer | Meaning | |---|---|---| | 4 MB body to an `/api/` route | JSON from the backend | nginx let it through; the old 1 MB default would have answered 413 | | 120 MB body to the same route | nginx's own 413 | the `110m` limit holds | | 120 MB body to `/api/campaigns/import` | `401 {"detail":"Not authenticated"}` from the backend | the import route has no nginx limit, and the backend refuses before reading the body | **Not done:** a signed-in upload on dev. Dev needs a real login, which I do not have. A signed-in 4.1 MB upload was confirmed earlier in a seeded stack built from main. **Production** keeps the 1 MB default until 4.5.0.
Sign in to join this conversation.
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
rbrooks/Quest-Board#1057
No description provided.