HTTP

Debug Guide

Debugging 500 Internal Server Error

500 means something broke on the server. The browser only sees the generic error — the real cause is always in your server logs.

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.

PlatformLog location
Node.js / Expressstdout/stderr, or your logging service
DjangoDEBUG=True shows it in browser (dev only); production logs
nginx/var/log/nginx/error.log
Apache/var/log/apache2/error.log
Dockerdocker logs <container>
AWS LambdaCloudWatch Logs
Vercel / NetlifyFunction logs in dashboard
Herokuheroku logs --tail
# Tail logs in real time while reproducing the error
tail -f /var/log/nginx/error.log
# or
docker logs -f my-app

    

Step 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 ECONNREFUSED or Too 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 undefined errors 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.py

    

Out of memory / resource exhaustion

Look for JavaScript heap out of memory or 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

Frequently Asked Questions

Where do I find the actual error for a 500?

Check your server logs — not the browser. The browser only sees the generic 500 response. The real error (stack trace, exception message) is in your application logs, which are typically in /var/log/nginx/error.log, /var/log/apache2/error.log, or your application's logging output (stdout/stderr in containers, CloudWatch in AWS, etc.).

Why does my 500 error only happen in production, not locally?

Common reasons: missing environment variables (API keys, database URLs), different Node.js/Python versions, missing dependencies not in package.json/requirements.txt, file path differences, or database connection limits being hit under real load. Compare your local and production environment configurations carefully.

How do I prevent 500 errors from exposing sensitive information?

Never send stack traces or internal error details to clients in production. Log them server-side and return a generic message. In Express.js, use a global error handler. In Django, set DEBUG=False. In Next.js, catch errors in route handlers and return sanitized responses.