Skip to main content
503

Service Unavailable

Active
RFC 9110 §15.6.4Since 1997Load balancers, maintenance pages, overloaded servers, circuit breakers, Kubernetes, cloud scaling events

HTTP 503 Service Unavailable indicates the server is temporarily unable to handle the request due to overload or scheduled maintenance. Defined in RFC 9110 §15.6.4. The server SHOULD include a Retry-After header. Search engines treat 503 as temporary and will not deindex — unlike 500, the server is communicating intentional unavailability.

Description

The 503 Service Unavailable status code indicates that the server is currently unable to handle the request due to a temporary overload or scheduled maintenance, which will likely be alleviated after some delay. The server SHOULD send a Retry-After header field to suggest an appropriate time for the client to retry the request.

503 is fundamentally different from 500 and 502. A 500 means the server encountered an unexpected bug or crash — it didn't plan to fail. A 502 means a proxy or gateway received an invalid response from an upstream server. A 503 means the server KNOWS it is unavailable and is communicating that fact intentionally. It's a controlled, deliberate signal: 'I'm alive, I understand your request, but I cannot serve it right now.'

This distinction has critical SEO implications. Search engine crawlers treat 503 as a temporary condition — they will return later to re-crawl. A 500, by contrast, signals instability and repeated 500s can lead to deindexing. During planned maintenance, serving a maintenance page with a 200 status code is a common mistake — crawlers will index the maintenance page content. The correct approach is returning 503 with a Retry-After header so crawlers know to come back without replacing your indexed content.

In modern infrastructure, 503 appears at multiple layers. Load balancers return 503 when all backend instances are unhealthy. Kubernetes ingress controllers return 503 during rolling deployments or when pods are in CrashLoopBackOff. Circuit breakers return 503 to shed load when downstream dependencies are failing. Cloud providers return 503 during auto-scaling events when new capacity hasn't yet registered. Each layer adds its own 503 semantics, and distinguishing which layer generated the 503 is a critical debugging skill.

The Retry-After header can specify either a number of seconds (Retry-After: 120) or an HTTP-date (Retry-After: Sat, 01 Jun 2025 08:00:00 GMT). Well-behaved clients respect this header and implement exponential backoff. Aggressive retry behavior against a 503 can worsen an overload situation — a thundering herd of retries hitting the moment Retry-After expires is a known failure pattern that requires jitter in retry logic.

Examples

Request during planned maintenance
http
GET /api/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Accept: application/json
503 response — maintenance with Retry-After (seconds)
http
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
Retry-After: 1800
X-Maintenance-Window: 2025-01-15T02:00:00Z/2025-01-15T04:00:00Z

{"error": "service_unavailable", "message": "Scheduled maintenance in progress. Estimated completion: 04:00 UTC.", "retry_after_seconds": 1800, "status_page": "https://status.example.com"}
Request hitting overloaded service
http
POST /api/reports/generate HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer token456

{"report_type": "annual", "year": 2024, "format": "pdf"}
503 response — overload with Retry-After (HTTP-date)
http
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
Retry-After: Wed, 15 Jan 2025 10:30:00 GMT
X-RateLimit-Reset: 1736936400

{"error": "service_overloaded", "message": "Report generation queue is full. Please retry after 10:30 UTC.", "queue_depth": 847, "max_queue_depth": 500, "estimated_wait_seconds": 900}
Load balancer returning 503 — all backends unhealthy
http
GET /health HTTP/1.1
Host: app.example.com
503 from load balancer layer
http
HTTP/1.1 503 Service Unavailable
Content-Type: text/html
Retry-After: 30
X-Served-By: lb-us-east-1a
X-Backend-Status: 0/4 healthy

<html><body><h1>Service Temporarily Unavailable</h1><p>All backend servers are currently unhealthy. Retrying automatically.</p></body></html>

Edge Cases

  • Always include a Retry-After header — without it, clients have no guidance and may retry aggressively, worsening the overload (thundering herd problem).
  • 503 during maintenance MUST return 503, not 200 with a maintenance page body. Returning 200 causes search engines to index your maintenance HTML as the actual page content, replacing your real content in search results.
  • Load balancers (ALB, nginx, HAProxy) return their own 503 when all backends fail health checks — this 503 comes from the LB layer, not the application. Distinguish via response headers (X-Served-By, Server, Via).
  • Kubernetes returns 503 during rolling deployments when old pods are terminating and new pods haven't passed readiness probes. This is transient (seconds) and doesn't require client-side retry logic beyond standard exponential backoff.
  • Circuit breaker pattern: services return 503 when downstream dependencies (databases, third-party APIs) are failing. The 503 protects the failing dependency from additional load while signaling clients to back off.
  • Cloud auto-scaling events (AWS ECS/EKS, GCP Cloud Run, Azure Container Instances) produce brief 503 windows when demand spikes faster than new instances can register with load balancers. Retry-After: 10-30 seconds is appropriate.
  • CDNs and edge networks (Cloudflare, CloudFront) cache 503 responses by default with short TTLs. Ensure your 503 response includes Cache-Control: no-store or a very short max-age to prevent stale 503s being served after recovery.
  • A 503 with no response body at all (connection reset, empty response) typically means the server process crashed before it could generate the 503 — this is closer to a 500 scenario even though the load balancer labels it 503.
  • Rate limiting should use 429, not 503. However, global service overload (affecting all users equally) is correctly represented as 503 — the distinction is per-client throttling (429) vs. systemic capacity exhaustion (503).

When You'll See This

  • Planned maintenance window — deploying database migrations that require downtime
  • Server under extreme load — request queue depth exceeds configured threshold
  • All backend instances behind a load balancer failing health checks simultaneously
  • Kubernetes rolling deployment — old pods terminating, new pods not yet ready
  • Circuit breaker open — downstream database or third-party API is unreachable
  • Cloud auto-scaling in progress — new instances launching but not yet registered
  • Database connection pool exhausted — all connections in use, none available
  • DDoS mitigation active — legitimate traffic being shed to protect core infrastructure
  • Dependency failure cascade — a critical microservice is down, causing upstream services to return 503

Implementation References

LanguageConstant
Gohttp.StatusServiceUnavailable
Rusthttp::StatusCode::SERVICE_UNAVAILABLE
Pythonhttp.HTTPStatus.SERVICE_UNAVAILABLE
Node.jshttp.STATUS_CODES[503]
.NETHttpStatusCode.ServiceUnavailable
JavaHttpURLConnection.HTTP_UNAVAILABLE
PHPResponse::HTTP_SERVICE_UNAVAILABLE (Symfony)
Ruby:service_unavailable (Rack)

History

Introduced in HTTP/1.1 (RFC 2068, 1997) to differentiate intentional unavailability from unexpected server errors. Refined in RFC 2616 (1999) which formalized the Retry-After header recommendation. Current definition in RFC 9110 (June 2022) maintains the same semantics with clearer language around the SHOULD-level requirement for Retry-After.

Related Status Codes

Related Headers

FAQ

What is the difference between 500, 502, and 503?

500 means the server hit an unexpected bug or crash — it didn't intend to fail. 502 means a proxy or gateway received an invalid response from an upstream server — the proxy is fine but the backend isn't responding correctly. 503 means the server KNOWS it is unavailable and is communicating that intentionally — overload, maintenance, or a deliberate circuit-breaker decision. The key distinction: 503 implies awareness and temporariness. The server will recover. 500 implies something broke unexpectedly.

Why is 503 critical for SEO during maintenance?

Search engine crawlers interpret status codes to decide what to do with indexed pages. A 503 tells crawlers 'this page still exists, I just can't serve it right now — come back later.' They retain your indexed content and return to re-crawl. If you serve a maintenance page with a 200 status, crawlers assume THAT is your new content and may replace your indexed pages with the maintenance HTML. If you return 500 repeatedly, crawlers may assume your site is broken and eventually deindex pages. 503 with Retry-After is the only correct approach for planned downtime.

How should clients implement retry logic for 503 responses?

First, respect the Retry-After header if present — either as seconds or as an HTTP-date. If no Retry-After is provided, implement exponential backoff starting at 1-2 seconds with a maximum of 60-120 seconds. Always add random jitter (±20-30%) to prevent thundering herd problems where all clients retry simultaneously when the server recovers. Set a maximum retry count (typically 3-5 attempts) to avoid infinite retry loops. For idempotent requests (GET, PUT, DELETE), automatic retries are safe. For non-idempotent requests (POST), only retry if the server returned 503 before processing began — check for idempotency keys or request IDs in the response.