All guides

LEARN \u00b7 DEBUGGING GUIDE

File upload works locally but fails in production: how to debug it

Users upload a file. On your machine, it saves to `./uploads/` and works. In production, the upload fails with a permission error, a timeout, or a silently empty file.

IntermediateWorks locally, fails in production

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.

( 01 )Fast diagnosis

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.
( 02 )Where to look

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
( 03 )Common root causes

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
( 04 )Fix patterns

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
( 05 )How to verify

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.
( 06 )Mistakes to avoid

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)