# Why "Submit failed 413" and "Upload failed" still happen in production QA keeps reporting that the following two actions fail on the deployed VPS while working locally: - **Resource Manager → Upload**: toast shows "Upload failed" (no details). - **Generation → Submit module as exam for approval**: toast shows "Submit failed — 413". Both are the same root cause: the **outer nginx reverse proxy** on the VPS (the one terminating TLS in front of the docker stack) is still using its default `client_max_body_size 1m` and short proxy timeouts. We already raised the inner limits in: - `odoo.conf` / `odoo-docker.conf` → `limit_request 134217728` (128 MB) + `limit_time_real 900s` - `frontend/Dockerfile` → `client_max_body_size 128m` + `proxy_read_timeout 900s` + `proxy_request_buffering off` But the **outer** nginx wasn't changed, so it still rejects the request (413) or times the proxied connection (504) before traffic ever reaches Odoo. Because the refusal happens at the proxy, nothing shows up in the Odoo logs — only in `/var/log/nginx/*.error.log`. ## Fix on the VPS (one-time, 2 minutes) 1. SSH to the VPS and back up the existing config: ```bash sudo cp /etc/nginx/sites-available/encoach /etc/nginx/sites-available/encoach.bak ``` 2. Copy the template from the repo and edit the `server_name` + TLS cert paths to match the real hostnames: ```bash sudo cp deploy/nginx-vps.conf.example /etc/nginx/sites-available/encoach sudo ln -sf /etc/nginx/sites-available/encoach /etc/nginx/sites-enabled/encoach sudo nginx -t sudo systemctl reload nginx ``` The critical lines added are: ```nginx client_max_body_size 128m; # matches Odoo limit_request client_body_timeout 900s; proxy_read_timeout 900s; # matches Odoo limit_time_real proxy_send_timeout 900s; proxy_request_buffering off; # stream uploads, don't buffer 100% on disk ``` 3. Smoke-test: ```bash # Upload a ~30 MB PDF through /api/resources — should return 200, not 413. # Submit a module from /generation — should return 201, not 413. tail -f /var/log/nginx/encoach.error.log # watch for "client intended to send too large body" tail -f /var/log/odoo/odoo-server.log # watch for the actual request hitting Odoo ``` ## If the 413 persists after the nginx change The body is getting rejected by something upstream. Check in order: 1. **Cloudflare / WAF** — Cloudflare's default free-plan body cap is 100 MB for authenticated endpoints and as low as 1 MB on some enterprise rules. In the dashboard → Rules → Transform / Firewall, look for `request.body.size` restrictions. 2. **Odoo worker (`limit_request`)** — verify `grep limit_request /etc/odoo/odoo.conf` shows at least `134217728`. If the config file was baked into the docker image and never rebuilt, `docker compose build backend && docker compose up -d backend`. 3. **`ModSecurity` / CRL** — some VPS providers ship ModSecurity enabled by default (`SecRequestBodyLimit`, default 13 MB). ## If the 504 persists 504 = the upstream didn't respond in time. - Confirm `proxy_read_timeout 900s` is actually in the running config (`nginx -T | grep proxy_read_timeout`). - Confirm `limit_time_real` in `odoo.conf` is at least 900 (default is 120; a single AI generation with 4 modules can exceed 3 minutes). - For first-time submissions, Odoo may need to warm up its AI clients. Retry once after a minute. ## What the UI now tells the user The frontend toast has been upgraded (see `lib/api-client.ts::describeApiError`). Users will now see, for example: - **413**: _"The module is too large for the server to accept (HTTP 413). Try a smaller file / fewer tasks, or ask an admin to raise the nginx client_max_body_size and Odoo limit_request."_ - **504**: _"The server took too long to respond (HTTP 504). The module may still be processing — wait a minute and reload before retrying."_ - **502**: _"The backend is unreachable (HTTP 502). Odoo may be restarting — try again in a moment."_ - **401**: _"Your session expired — please sign in again."_ This applies to: - Resource upload (admin + teacher library) - Exam generation submit - Custom-exam publish / save draft