X Xerobit

HTTP 5xx Server Error Codes — 500, 502, 503, 504 Explained

HTTP 5xx codes indicate server-side failures. Learn when 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, and 504 Gateway Timeout occur, how to handle them...

Mian Ali Khalid · · 4 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 5xx codes tell clients the server failed to fulfill a valid request. Unlike 4xx errors, 5xx errors are not the client’s fault — they indicate the server is broken, overloaded, or a dependency failed.

Use the HTTP Status Codes Reference to look up any status code.

500 Internal Server Error

The generic server error — an unhandled exception or bug:

500 — Triggered by:
- Unhandled exception in application code
- Database query throwing an unexpected error  
- Null pointer / undefined property access
- Misconfigured application (wrong env vars)
- Out of memory errors

What clients should do: Not retry immediately (the error will recur). Show a user-friendly error. Log the requestId for support.

What servers should do: Never expose stack traces or internal details:

// WRONG: leaks internal details
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.stack });
});

// RIGHT: log internally, return generic message
app.use((err, req, res, next) => {
  console.error({ err, requestId: req.id });
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
      requestId: req.id,
    }
  });
});

501 Not Implemented

The server doesn’t support the requested functionality:

501 — Use when:
- HTTP method is not implemented at all (e.g., TRACE is disabled)
- Feature is planned but not yet built

Rarely used in practice — 405 (Method Not Allowed) is more common for unsupported methods on specific endpoints.

502 Bad Gateway

A reverse proxy or gateway received an invalid response from an upstream server:

502 — Triggered by:
- Load balancer can't connect to application server
- Nginx can't reach the upstream Node.js/Python process
- Microservice called by your API is down
- Upstream returned a malformed HTTP response
# Example: Nginx returns 502 when upstream is down:
upstream app {
  server 127.0.0.1:3000;
}

server {
  location / {
    proxy_pass http://app;
    # If app is down → 502 Bad Gateway
  }
}

What clients should do: Wait and retry with exponential backoff. The upstream service is likely recovering.

503 Service Unavailable

The server is temporarily unable to handle requests — overloaded or down for maintenance:

503 — Triggered by:
- Server is under too much load
- Maintenance mode / deployment in progress
- Circuit breaker tripped to protect a failing service
- Database connection pool exhausted
HTTP/1.1 503 Service Unavailable
Retry-After: 300
Content-Type: application/json

{"error": {"code": "SERVICE_UNAVAILABLE", "message": "Temporarily down for maintenance"}}

Always include Retry-After when you know when the service will recover.

// Maintenance middleware:
app.use((req, res, next) => {
  if (process.env.MAINTENANCE_MODE === 'true') {
    return res.status(503)
      .set('Retry-After', '600')
      .json({ error: { code: 'MAINTENANCE', message: 'Under maintenance until 14:00 UTC' }});
  }
  next();
});

504 Gateway Timeout

The gateway/proxy didn’t receive a response from the upstream in time:

504 — Triggered by:
- Upstream service is too slow (long DB query, heavy computation)
- Network timeout between services
- Upstream service is frozen but not down (would get 502 if crashed)
# Nginx timeouts:
proxy_connect_timeout 10s;   # Time to establish connection
proxy_read_timeout 60s;       # Time waiting for upstream to respond
proxy_send_timeout 30s;       # Time to send request

# If proxy_read_timeout exceeded → 504 Gateway Timeout

What clients should do: Retry with backoff. The operation may or may not have completed on the server (unlike 503, which definitely didn’t process it).

Retry logic for 5xx errors

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  const RETRYABLE = [500, 502, 503, 504];
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const res = await fetch(url, options);
    
    if (res.ok) return res;
    
    if (!RETRYABLE.includes(res.status) || attempt === maxRetries) {
      throw new Error(`HTTP ${res.status}`);
    }
    
    // Exponential backoff: 1s, 2s, 4s
    const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
    
    // Check Retry-After header:
    const retryAfter = res.headers.get('Retry-After');
    const waitMs = retryAfter ? parseInt(retryAfter) * 1000 : delay;
    
    await new Promise(r => setTimeout(r, waitMs));
  }
}

Quick reference

CodeNameWhen it occursClient action
500Internal Server ErrorBug, unhandled exceptionShow error, no retry
501Not ImplementedUnsupported method/featureDon’t retry
502Bad GatewayUpstream unreachableRetry with backoff
503Service UnavailableOverloaded, maintenanceRetry after Retry-After
504Gateway TimeoutUpstream too slowRetry with backoff

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.