What this usually means
Local development usually stores files on the local filesystem with generous permissions and no size restrictions. Production environments have different constraints: serverless functions have read-only filesystems except `/tmp`, container filesystems are ephemeral (files disappear on restart), load balancers have request size limits, and production servers have tighter security permissions. The upload code that writes to `./uploads/` locally cannot do the same in production.
The first ten minutes \u2014 establish facts before touching code.
- 1Check the exact error message. 'EACCES' or 'permission denied' means the process cannot write to the target directory.
- 2Check the deployment environment type. Serverless (Vercel, Lambda, Cloudflare Workers) have no persistent writable filesystem.
- 3Check the request size. Load balancers, reverse proxies, and platforms have default max request body sizes (often 1-10 MB).
- 4Check if the upload directory exists in production. Local code might create it on startup, but production might not run that code.
- 5Check multipart/form-data handling. Some platforms parse multipart differently or have stricter limits on field sizes.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchServer error logs — the exact error message and stack trace from the upload handler
- searchDeployment platform docs — filesystem limitations, `/tmp` availability, max request size
- searchLoad balancer / reverse proxy config — `client_max_body_size` (Nginx), max request size setting
- searchCode that handles the uploaded file — where is it saved? Is the directory writable?
- searchObject storage config (S3, GCS, Azure Blob) — bucket permissions, region, access keys
- searchContainer or function configuration — memory limit, timeout, ephemeral storage size
Practical causes, not theory. These are the things you will actually find.
- warningWriting to a local filesystem path that does not exist or is not writable in production
- warningPlatform runs on serverless functions with no persistent writable filesystem
- warningLoad balancer or API gateway has a smaller max request size than the uploaded files
- warningContainer restarts or scales, and uploaded files stored on local disk are lost
- warningS3 or object storage credentials are not configured in production
- warningMultipart upload timeout — the server's timeout is shorter than the upload time for large files
- warningThe upload directory is not created at startup in the production environment
Concrete fix directions. Pick the one that matches your root cause.
- buildUse object storage (S3, GCS, R2) for file uploads instead of local disk — it works everywhere
- buildGenerate presigned upload URLs so the client uploads directly to object storage, bypassing the server
- buildIf you must use disk, write to `/tmp` for serverless environments (it is the only writable path)
- buildIncrease the load balancer and server request size limits to accommodate expected file sizes
- buildAdd startup logic that creates the upload directory with correct permissions if it does not exist
A fix you cannot prove is a guess. Close the loop.
- verifiedUpload a small test file in production and confirm it is stored correctly.
- verifiedUpload a file near the size limit and confirm it succeeds or fails with a clear error.
- verifiedCheck that uploaded files persist after a deployment or container restart.
- verifiedVerify the upload works from the client through the load balancer end-to-end.
- verifiedTest with different file types and sizes in a staging environment that mirrors production.
Things that make this bug worse or harder to find.
- warningSaving uploads to local disk in production — containers and serverless functions are ephemeral
- warningNot setting a maximum file size limit on the server — a single large upload can crash the process
- warningNot handling upload errors with a user-friendly message — a 500 is not helpful
- warningStoring uploads in the application directory — it gets wiped on every deploy
- warningNot scanning uploaded files for malware (if user-uploaded files are stored and served back)