HTTP 503 Service Unavailable — Server Overload and Maintenance
Handle HTTP 503 Service Unavailable errors. Learn about server overload management, maintenance pages, Retry-After headers, and circuit breaker patterns.
Detailed Explanation
HTTP 503 Service Unavailable
A 503 error indicates the server is temporarily unable to handle the request. Unlike 500 (unexpected failure), 503 is often intentional -- the server knows it cannot serve the request right now.
Common Causes
1. Server overload: The server has too many concurrent connections or requests to handle. Resources (CPU, memory, threads) are exhausted.
# Check server load
uptime
top
free -h
2. Maintenance mode: Many applications intentionally return 503 during deployments or maintenance:
Retry-After: 300 # Try again in 5 minutes
3. Circuit breaker open: Application-level circuit breakers return 503 when a downstream dependency is failing:
if (circuitBreaker.isOpen()) {
res.status(503).json({
error: "Service temporarily unavailable",
retryAfter: circuitBreaker.resetTimeout / 1000
});
}
4. Rate limiting (alternative to 429): Some services return 503 instead of 429 for rate limiting.
Best Practices
1. Include Retry-After header:
HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: application/json
{"error": "Server is undergoing maintenance", "retryAfter": 120}
2. Implement graceful degradation:
- Return cached data instead of 503
- Disable non-critical features under load
- Queue requests for processing when capacity returns
3. Auto-scaling:
- Scale horizontally when load increases
- Use Kubernetes HPA or cloud auto-scaling groups
- Pre-scale for anticipated traffic spikes
4. Health check endpoints:
app.get('/health', (req, res) => {
if (isOverloaded()) {
res.status(503).json({ status: 'overloaded' });
} else {
res.status(200).json({ status: 'ok' });
}
});
Client-Side Handling
async function fetchWithRetry(url) {
const response = await fetch(url);
if (response.status === 503) {
const retryAfter = response.headers.get('Retry-After');
const delay = retryAfter ? parseInt(retryAfter) * 1000 : 5000;
await new Promise(r => setTimeout(r, delay));
return fetchWithRetry(url); // Retry after delay
}
return response;
}
Use Case
503 errors are a normal part of production operations during deployments, traffic spikes, and dependency failures. Understanding how to properly return 503 with Retry-After headers, implement circuit breakers, and design graceful degradation strategies directly impacts application availability and user experience during partial outages.