How HTTP Works
5xx · Server error
< HTTP/1.1 503 Service Unavailable

HTTP 503 Service Unavailable: Causes, Fixes and Retry-After

Fix HTTP 503 Service Unavailable: nginx no live upstreams, Kubernetes endpoints, ALB healthy hosts, Cloudflare, and a maintenance page with Retry-After.

Reviewed 6 min readintermediate5 sourcesTry itMarkdown
Cacheable
Only with explicit freshness
Retry?
Yes: honor Retry-After
Usually sent by
Origin, load balancer or CDN
Spec
RFC 9110 §15.6.4
Often confused with
429, 502
On this page

TL;DR: HTTP 503 Service Unavailable means the server cannot handle your request right now, often because of overload or maintenance. Find who answered, check backend health, and if you serve 503 on purpose, send Retry-After.

What it means

RFC 9110 section 15.6.4: the server is currently unable to handle the request due to temporary overload or scheduled maintenance. The key difference from 500 is that 503 says retrying later is expected to work. Some proxies also use it when no backend is available. An ALB with all targets unhealthy can fail open and route to them.

Server responses and client messages below are illustrative.

HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: text/html

What you see in your client

These messages assume the response reason is Service Unavailable; a server can use different wording.

  • Axios: AxiosError: Request failed with status code 503. With the default validateStatus, Axios rejects this response. Inspect error.response.status, error.response.headers and error.response.data before changing the request. Axios source.
  • fetch: the promise resolves with response.status === 503 and response.ok === false. Check ok yourself; a catch block alone misses HTTP error responses. Fetch behaviour.
  • Python requests: requests.exceptions.HTTPError: 503 Server Error: Service Unavailable for url: https://example.com/api. This appears when you call response.raise_for_status(). Save the response body and headers before raising. Requests source.
  • curl -f: curl: (22) The requested URL returned error: 503. Use curl -sS -D - https://example.com/api while diagnosing so you retain the error body. curl source.
  • .NET: System.Net.Http.HttpRequestException: Response status code does not indicate success: 503 (Service Unavailable). This is the English message from EnsureSuccessStatusCode() with that reason phrase. Inspect StatusCode and read the body before calling it. Runtime source, message resource.
  • Spring WebClient: org.springframework.web.reactive.function.client.WebClientResponseException$ServiceUnavailable: 503 Service Unavailable from GET https://example.com/api. retrieve() uses this subclass by default; read getResponseBodyAsString(). RestTemplate uses HttpServerErrorException.ServiceUnavailable for this 5xx status. WebClient source, RestTemplate handler.

Who sent it?

SignalLayer and typical cause
Server: nginx, page “503 Service Temporarily Unavailable”nginx: limit_req/limit_conn rejection, or a deliberate return 503
Server: awselb/2.0 and an empty or minimal bodyALB: no registered targets, or all registered targets are unused
ingress-nginx page, log Service "ns/name" does not have any active EndpointKubernetes: no Ready pods behind the Service
Server: cloudflare with an origin error page, or Cloudflare branded pageOrigin sent 503, or Cloudflare generated a data-center connectivity error
X-Cache: Error from cloudfront, “The request could not be satisfied”Origin overload, CloudFront capacity or an edge-function limit/error
App log shows pool exhausted, queue fullThe application itself is shedding load

nginx request-limit log (sample request identifiers):

[error] 29#29: *513 limiting requests, excess: 10.300 by zone "api", client: 203.0.113.9

Common causes by stack

  • nginx: limit_req_status and limit_conn_status both default to 503. A burst beyond limit_req’s allowance is a rejection; inspect limiting requests in the error log. For deliberate per-client throttling, configure 429. Request limits, connection limits.
  • Laravel: php artisan down --retry=60 activates maintenance and supplies Retry-After. Run php artisan up when deployment is complete; check that every serving instance has left maintenance. Laravel maintenance mode.
  • ASP.NET Core: rate-limiting middleware’s RejectionStatusCode defaults to 503. If the rejection body says too many concurrent requests, inspect the limiter policy and queue before adding retries; set 429 for a per-client quota. RateLimiterOptions.
  • Kubernetes ingress-nginx: an empty backend produces 503; the controller warns Service "namespace/name" does not have any active Endpoint. Check selectors and readiness, then inspect EndpointSlices. An existing endpoint with a refused port points toward 502 instead. Controller source, Service debugging.
  • AWS ALB: no registered targets or all targets unused produces ALB-generated 503. All targets merely unhealthy is different: ALB can fail open. ALB errors, health-check behaviour.
  • Cloudflare/CloudFront: identify the error body before changing origin capacity. Cloudflare documents its own 503 connectivity errors; CloudFront lists origin overload, edge capacity and function execution limits. Cloudflare 503, CloudFront 503.

Fix it

  1. Confirm the backends are up and passing health checks: curl -sv http://BACKEND:PORT/health from the proxy host.
  2. nginx limit_req / limit_conn: both reject with 503 by default. Set limit_req_status 429; and limit_conn_status 429; so clients see the right code (see 429).
  3. Kubernetes: inspect kubectl get endpointslices -l kubernetes.io/service-name=my-svc -o yaml, then kubectl describe pod POD. Match Ready endpoints to the Service selector and port. Check rollout capacity and the termination grace period when failures coincide with deploys.
  4. ALB: in the target group, check “healthy host count” and the health check path, port and success codes; a 503 from the ALB itself with no targets registered is a configuration issue, not load.
  5. Overload: look at CPU, memory, database connection pool size and worker counts (gunicorn --workers, PHP-FPM pm.max_children); autoscale or shed load deliberately.
  6. Cloudflare in front: if the 503 body is your origin’s, fix the origin; if it contains cloudflare or cloudflare-nginx, use Cloudflare’s 503 troubleshooting path. For distinct origin connection failures, inspect the 52x codes (see 520 to 524).

Return 503 on purpose: maintenance mode

Send Retry-After, mark the response uncacheable, and keep your health endpoint working. Create /var/www/maintenance/index.html before enabling this nginx configuration; its named error location preserves the 503 status. Retry-After is either seconds or an HTTP date:

server {
    error_page 503 @maintenance;

    location / {
        return 503;
    }

    location = /health {
        return 200 "ok\n";
    }

    location @maintenance {
        add_header Retry-After 3600 always;
        add_header Cache-Control "no-store" always;
        root /var/www/maintenance;
        rewrite ^ /index.html break;
    }
}
// Express: shed load or enter maintenance with a correct Retry-After
app.use((req, res, next) => {
  if (!maintenanceMode) return next()
  res.set({ 'Retry-After': '3600', 'Cache-Control': 'no-store' })
  res.status(503).type('text/html').send('<h1>Back soon</h1>')
})

Clients must accept both forms of the header:

function retryAfterMs(res) {
  const v = res.headers.get('retry-after')
  if (!v) return null
  const n = Number(v)
  if (Number.isFinite(n)) return n * 1000
  const t = Date.parse(v)
  return Number.isNaN(t) ? null : Math.max(0, t - Date.now())
}

SEO during maintenance

Serve temporary maintenance at the original URLs with 503 and Retry-After. Google reduces crawling for 5xx responses, ignores their response content and initially preserves indexed URLs; persistent errors eventually remove them. Returning 200 makes the maintenance body eligible for indexing. Restore normal responses promptly rather than redirecting every URL to one maintenance page. Google’s HTTP guidance.

Retrying a concurrency rejection

For a 503 body containing "too many concurrent requests", reduce parallel requests and queue work on the client. Wait for Retry-After when supplied; otherwise use bounded exponential backoff with jitter. Each retry consumes capacity too. For a POST that changes state, check its result or use the service’s idempotency mechanism before repeating it.

Reproduce

curl -sI https://example.com/ | grep -iE '^(HTTP|retry-after|server|via|x-cache|cf-ray)'

See also the comparison 502 vs 503 vs 504: how to tell the three gateway errors apart from the nginx error log and Cloudflare codes.

Frequently asked questions

What does 503 Service Unavailable mean?

The server cannot handle the request right now because it is overloaded, in maintenance, or has no healthy backend to pass it to. RFC 9110 treats it as temporary, and a server may send Retry-After to say when to come back.

How do I fix nginx 503 Service Temporarily Unavailable?

Check the nginx error log. A limit_req or limit_conn rejection returns 503 by default, a maintenance flag or return 503 rule may be active, and deliberate maintenance middleware may return it. A stopped upstream commonly causes 502 instead. ("no live upstreams" is a 502 in nginx, not a 503; see the 502 page.)

How long does a 503 error last?

It lasts as long as the cause: seconds for a rolling deploy or autoscale, minutes to hours for maintenance, until the incident is fixed for outages. If Retry-After is present, it is the server estimate; if not, use exponential backoff.

Does a 503 hurt SEO?

A short 503 with Retry-After is the correct signal for maintenance and Google slows crawling and keeps the pages indexed. Persistent 5xx responses can eventually remove URLs from the index, so restore normal responses promptly.

What is the difference between 500 and 503?

500 means the application hit an unexpected error handling the request. 503 means it could not take the request at all (overload, maintenance, no healthy backend) and retrying later is expected to work.

What does a 503 saying too many concurrent requests mean?

The service is rejecting simultaneous work. Reduce client parallelism, queue requests and respect Retry-After when supplied; otherwise use bounded backoff with jitter. Check the result of a state-changing request before repeating it.

Why is my Kubernetes ingress returning 503?

Usually the Service has no ready endpoints: pods are failing readiness probes or the selector does not match any pod. A wrong targetPort can instead leave endpoints present but cause a connection failure and 502. ingress-nginx logs Service "namespace/name" does not have any active Endpoint.

Sources

  1. MDN Web Docs: 503 Service Unavailabledeveloper.mozilla.org
  2. RFC 9110 Section 15.6.4: 503 Service Unavailablerfc-editor.org
  3. RFC 9110 Section 10.2.3: Retry-Afterrfc-editor.org
  4. Google Search Central: HTTP status codes and network errorsdevelopers.google.com
  5. AWS: Troubleshoot Application Load Balancer HTTP 503docs.aws.amazon.com

Keep going

Browse /search