Why Do I Only Get 500 Errors in Production, Never on My Machine?
6 min read
A 500 error that only shows up in production almost always means the server hit a real error, but your host is hiding the message from visitors on purpose. The actual error is not gone. It is sitting in your host's logs. The fix is to go read those logs, and nine times out of ten the cause is a missing environment variable or a difference between your machine and the deployed one.
Why this happens
"500 Internal Server Error" is the generic response a server gives when the code threw an exception it did not catch. In production, frameworks deliberately replace the detailed error with a blank 500 so you do not leak stack traces, file paths, or secrets to strangers. On your own machine you see the full message; deployed, everyone including you sees the sanitised version.
It only breaks in production because production is a different environment.
Your agent built code that worked with your local .env file, your local
database, and your installed tools. It never set up the deployed equivalent,
because you did not ask it to, and locally everything was already there. The
most common causes: an environment variable that exists on your laptop but not
on the host, a database the deployed server cannot reach, or a file path that
only exists in your project folder.
How to check
You are not going to guess this. You are going to read the real error.
- Open your host's logs. On Vercel: your project, then the failing deployment, then the Logs or Runtime Logs tab. On Netlify: Logs, then Functions. On Railway or Render: the Logs panel for the service.
- Reproduce the 500 by hitting the page or action that fails, then look at
the log line stamped with that moment. You want the stack trace, not the
500status line. - Read the top of the trace. It names the real problem in plain words:
undefined is not a function,ECONNREFUSED,relation "users" does not exist, orMissing environment variable: STRIPE_SECRET_KEY.
If your logs show nothing at all, that is its own finding: you have no visibility, and seeing logs in production is the first thing to fix.
The fix
- Match your environment variables. Compare your local
.envagainst the variables set in your host's dashboard (Vercel: Settings, Environment Variables). Every key your code reads must exist in production too. Add the missing ones, then redeploy, because most hosts only apply env changes on a fresh build. - Check anything that reads a secret or connects out. A
DATABASE_URL, Stripe key, or API token that is blank in production throws the instant it is used. This is the single most common 500 for vibe-coded apps. - Confirm the database is reachable.
ECONNREFUSEDor a connection timeout means the deployed server cannot see your database. Check that you are using the hosted connection string, notlocalhost, and that your database allows connections from your host. - Redeploy and hit the same route again. Watch the log stream live. Either the 500 is gone, or the trace has moved to the next problem. Repeat until the route returns cleanly.
The trap to avoid
Do not wrap the failing code in a try/catch that swallows the error and
returns a friendly message. That makes the 500 disappear from the visitor's
screen while the underlying action, saving the order, sending the email, still
silently fails. You have hidden the symptom and kept the bug. Read and fix the
real error first; add graceful handling afterwards, and always log the caught
error so it still reaches you.
Where this fits
A blank 500 on your first real user's first real action is the fastest way to lose them, and it is invisible in every local demo. This is core beta-readiness: knowing your production errors before a stranger does. The free Readiness Report checks whether your deployed app has readable logs and flags the environment gaps that cause exactly this, and the Finishing Pass hands you the fixes in order. If your logs turned out to be empty, start with error tracking for a vibe-coded app, and if the page loads but behaves wrong, see works locally but broken in production.