Step 1 — Find the Real Error
The browser shows "500 Internal Server Error" but that tells you nothing. The actual error is in your server logs.
| Platform | Log location |
|---|---|
| Node.js / Express | stdout/stderr, or your logging service |
| Django | DEBUG=True shows it in browser (dev only); production logs |
| nginx | /var/log/nginx/error.log |
| Apache | /var/log/apache2/error.log |
| Docker | docker logs <container> |
| AWS Lambda | CloudWatch Logs |
| Vercel / Netlify | Function logs in dashboard |
| Heroku | heroku logs --tail |
# Tail logs in real time while reproducing the error tail -f /var/log/nginx/error.log # or docker logs -f my-appStep 2 — Common Causes
Unhandled exception / missing try-catch
The most common cause. An exception is thrown and nothing catches it.
// ❌ No error handling app.get('/api/data', async (req, res) => { const data = await db.query('SELECT * FROM users') // throws if DB is down res.json(data) }) // ✅ Catch and handle app.get('/api/data', async (req, res) => { try { const data = await db.query('SELECT * FROM users') res.json(data) } catch (err) { console.error('DB query failed:', err) res.status(500).json({ error: 'Internal server error' }) } })Database connection failure
Look for errors like
ECONNREFUSEDorToo many connections. Check that the database is running and the connection string is correct in environment variables.Missing environment variable
A missing env var causes
undefinederrors at runtime. Validate all required env vars at startup.# Check what env vars are set printenv | grep DATABASE # or in Node.js console.log(process.env.DATABASE_URL)Syntax error in recently deployed code
A syntax error in a module causes the entire server to fail on startup or on first import.
# Node.js — check for syntax errors node --check src/app.js # Python python -m py_compile app.pyOut of memory / resource exhaustion
Look for
JavaScript heap out of memoryor similar. Increase memory limits or find the memory leak.File not found / wrong path
Paths that work locally may differ in production (especially in Docker). Use
path.join(__dirname, 'config.json')instead of relative paths.Step 3 — Add Proper Error Handling
Express.js — global error handler
// Must be defined AFTER all routes app.use((err, req, res, next) => { console.error({ message: err.message, stack: err.stack, path: req.path, method: req.method }) const status = err.status ?? 500 res.status(status).json({ error: status < 500 ? err.message : 'Internal server error' }) }) // Use express-async-errors to catch async throws automatically require('express-async-errors')Next.js App Router
// app/api/data/route.ts export async function GET() { try { const data = await fetchData() return Response.json(data) } catch (err) { console.error('GET /api/data:', err) return Response.json({ error: 'Internal server error' }, { status: 500 }) } }Django
# settings.py — production settings DEBUG = False LOGGING = { 'version': 1, 'handlers': { 'file': { 'class': 'logging.FileHandler', 'filename': '/var/log/django/error.log', }, }, 'root': { 'handlers': ['file'], 'level': 'ERROR', }, }Step 4 — Prevention Checklist
- All async operations wrapped in try/catch
- Global error handler catches unhandled exceptions
- Environment variables validated at startup (fail fast)
- Database connection errors handled gracefully
- Stack traces never sent to clients in production
- Health check endpoint (
/health) to verify DB/service connectivity - Structured logging with request context (path, method, user ID)
- Alerts configured for 5xx error rate spikes