X Xerobit

HTTP 500 Internal Server Error — What It Means and How to Debug It

HTTP 500 means the server encountered an unexpected error it couldn't handle. Unlike 4xx errors, 500 is always the server's fault. Here's how to diagnose and fix HTTP 500 errors.

Mian Ali Khalid · · 5 min read
Use the tool
HTTP Status Codes
Full HTTP status code reference with explanations and when to use each.
Open HTTP Status Codes →

HTTP 500 Internal Server Error means the server crashed, threw an unhandled exception, or otherwise failed to process the request. Unlike 404 (resource not found) or 400 (bad request), 500 is always the server’s fault — the client did nothing wrong.

Use the HTTP Status Codes reference for a complete guide to all HTTP response codes.

What causes HTTP 500 errors

The 500 status code is a catch-all for any server-side failure that doesn’t have a more specific code. Common causes:

Unhandled exceptions: The application threw an error that wasn’t caught:

// This throws an uncaught TypeError if user is null:
app.get('/users/:id', async (req, res) => {
  const user = await db.getUser(req.params.id);
  res.json(user.profile);  // TypeError if user is null → 500
});

Database errors: Query failed, connection pool exhausted, deadlock:

SQLSTATE[HY000]: General error: Deadlock found when trying to get lock

File system errors: Can’t read a file, disk full, permission denied:

Error: ENOENT: no such file or directory, open '/app/config.json'

Memory errors: Out of memory, segmentation fault in a native module.

Configuration errors: Missing environment variable, misconfigured server.

Dependency failures: External service returned an unexpected response that crashed the handler.

500 vs other 5xx codes

CodeNameMeaning
500Internal Server ErrorGeneral server failure
501Not ImplementedServer doesn’t support the request method
502Bad GatewayUpstream server returned invalid response
503Service UnavailableServer temporarily overloaded or in maintenance
504Gateway TimeoutUpstream server didn’t respond in time

502/503/504 typically indicate infrastructure problems (load balancer, proxy, upstream service). 500 indicates application code failures.

How to debug a 500 error

Step 1: Check the server logs

The 500 response goes to the client; the actual error goes to the server logs. Always check logs first:

# Node.js / Express:
tail -f /var/log/app/error.log

# Nginx:
tail -f /var/log/nginx/error.log

# Apache:
tail -f /var/log/apache2/error.log

# Docker:
docker logs container_name

# PM2 (Node.js process manager):
pm2 logs app-name --err

The error message, stack trace, and timestamp tell you exactly what went wrong.

Step 2: Reproduce locally

Reproduce the exact request that caused the 500:

# Using curl to replicate:
curl -X POST https://api.example.com/endpoint \
  -H 'Content-Type: application/json' \
  -d '{"key": "value"}'

Add detailed logging around the failure point to narrow down the cause.

Step 3: Check recent deployments

The most common cause of a 500 that wasn’t there yesterday: a deployment from today. Check what changed:

git log --oneline -10  # Recent commits
git diff HEAD~1        # Changes in last commit

Step 4: Check external dependencies

If the 500 started at a specific time, correlate with:

  • Database connection limits reached
  • Third-party API started returning errors
  • Memory or disk usage spikes
  • Scheduled job or batch process started at that time

Returning 500 correctly in code

Express (Node.js):

// Global error handler — catches all unhandled errors:
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err);
  
  // Don't expose error details to clients in production:
  const message = process.env.NODE_ENV === 'production' 
    ? 'Internal server error' 
    : err.message;
    
  res.status(500).json({ error: 'internal_server_error', message });
});

// Always use try/catch in async handlers:
app.get('/data', async (req, res, next) => {
  try {
    const data = await fetchData();
    res.json(data);
  } catch (err) {
    next(err);  // Pass to error handler
  }
});

FastAPI (Python):

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import logging

app = FastAPI()
logger = logging.getLogger(__name__)

@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    logger.error(f"Unhandled exception: {exc}", exc_info=True)
    return JSONResponse(
        status_code=500,
        content={"error": "internal_server_error", "message": "An unexpected error occurred"}
    )

What NOT to do with 500 errors

Don’t expose stack traces to clients:

// NEVER return this to API clients:
{
  "error": "TypeError: Cannot read properties of null (reading 'email')",
  "stack": "TypeError: Cannot read properties...\n    at /app/routes/users.js:45:32\n    at..."
}

Stack traces reveal:

  • Technology stack (Node.js, Python, Java)
  • File paths and code structure
  • Database query structure in some cases
  • Third-party library versions

All of this helps attackers profile your system. Log the stack trace server-side; return only a generic message to clients.

Don’t silently swallow errors:

// BAD — error disappears:
try {
  await riskyOperation();
} catch (e) {
  // Silently ignore
}

// GOOD — log and re-throw or return error:
try {
  await riskyOperation();
} catch (e) {
  logger.error('riskyOperation failed:', e);
  throw e;  // Let it become a 500 with proper logging
}

Monitoring for 500 errors

Set up alerts for sustained 500 error rates:

  • Error rate above 1% over 5 minutes → alert
  • Any 500 from a health check endpoint → alert

Tools: Datadog, New Relic, Sentry, Grafana with Prometheus.


Related posts

Related tool

HTTP Status Codes

Full HTTP status code reference with explanations and when to use each.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.