HTTP 503 is Service Unavailable: the server can’t handle the request right now because of a temporary overload or scheduled maintenance, and expects to recover after some delay (RFC 9110 section 15.6.4). It’s the 5xx that promises a “later”. The server may send Retry-After to say when; RFC 9110 also notes that an overloaded server doesn’t have to use 503 and may simply refuse connections.
The exchange
HTTP/1.1 503 Service Unavailable
Retry-After: 3600
Content-Type: text/html
<h1>Down for maintenance until 10:00 UTC</h1>
Retry-After takes seconds, or an HTTP date like Fri, 16 Oct 2026 10:00:00 GMT (RFC 9110 section 10.2.3). A client should wait at least that long, and back off exponentially when there’s no header. Idempotent requests are safe to retry.
Maintenance and search engines
For planned downtime, a 503 tells Googlebot to slow down, and indexed pages keep their place while the outage is short. If the errors go on, Google drops the URLs. A maintenance page served with 200 is worse: to Google it’s the new content of every URL that shows it. robots.txt matters too: Google reads a 5xx on /robots.txt as “the whole site is disallowed” until it gets a real answer, so crawling stops.
Who sends it
- nginx
limit_reqandlimit_connreject with 503 by default. For rate limits, setlimit_req_status 429;: that’s what they are. - AWS ALB answers 503 when the target group has no registered targets. With targets registered but all unhealthy, it fails open and sends traffic to all of them.
- Kubernetes ingress-nginx answers 503 when the Service has no ready endpoints: every pod failing its readiness probe, or a selector that matches nothing.
- Go’s
http.TimeoutHandleranswers 503 when a handler runs past its limit. - Your own app, when shedding load, or from a readiness endpoint during graceful shutdown, so the load balancer stops sending it traffic.
503 or another code
A dependency outage your app can’t work around is a fair 503. A bug is a 500, a rate limit is a 429, and a proxy whose upstream didn’t answer in time sends 504. 503 isn’t heuristically cacheable, though CloudFront caches it for 10 seconds by default.
In your stack
A maintenance switch in nginx:
server {
listen 80;
server_name example.com;
add_header Retry-After 3600 always; # without "always", add_header skips error responses
error_page 503 /maintenance.html;
location = /maintenance.html {
root /srv/www;
internal;
}
location / {
return 503;
}
}
res.set('Retry-After', '120').status(503).json({ error: 'maintenance' }); // Express
return "Down for maintenance", 503, {"Retry-After": "3600"} # Flask
w.Header().Set("Retry-After", "120")
http.Error(w, "down for maintenance", http.StatusServiceUnavailable)