503 Service Unavailable (tested)
503 Service Unavailable means the server is up but cannot handle the request right now. It is meant to be temporary. Pair it with a Retry-After header so clients and crawlers know when to come back.
1. What 503 means
A 503 says: the server got your request, but it is overloaded or down for maintenance. Try again later. It is not a bug in the request (that would be a 4xx). It is not a crash (that is usually 500 or 502).
The server can add Retry-After with a number of seconds or an HTTP date. The same header is used with 429; see our Retry-After and 429 guide.
2. A maintenance switch that returns 503
We wrote a tiny middleware in plain Node. When the flag is on, every request gets 503 and Retry-After: 120.
let MAINT = true;
function maintenance(req, res, next) {
if (MAINT && req.url !== '/health') {
res.writeHead(503, {
'Retry-After': '120',
'Content-Type': 'text/plain',
'Cache-Control': 'no-store'
});
return res.end('Down for maintenance. Back soon.\n');
}
next();
}
This is the real response we got:
$ curl -si http://127.0.0.1:8990/site/
HTTP/1.1 503 Service Unavailable
Retry-After: 120
Content-Type: text/plain
Cache-Control: no-store
Date: Fri, 02 Oct 2026 02:39:44 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
Down for maintenance. Back soon.
Cache-Control: no-store stops a CDN from caching the maintenance page after you switch it off. We left /health out of the switch so your load balancer does not mark the box dead.
3. Does curl retry a 503? Yes, and it waits for Retry-After
We pointed curl at a server that always answers 503 with Retry-After: 2 and logged each hit.
$ curl -s -o /dev/null -w '%{http_code}\n' --retry 3 http://127.0.0.1:8990/
503
# elapsed: 8028 ms
# server saw 4 hits, gaps between them: 2004, 2004, 4005 ms
So --retry 3 means 1 try plus 3 retries. The first two gaps matched Retry-After: 2. The last gap was 4 s. That looks like curl's own doubling back-off (1, 2, 4 s) being longer than Retry-After, so it used the longer one.
We also tried --retry 2 --retry-delay 1. The server still saw gaps of 2003 and 2004 ms. Retry-After won over the shorter delay we asked for.
curl prints a warning while it waits:
Warning: Problem : HTTP error. Will retry in 2 seconds. 1 retry left.
Without --retry, curl sent exactly 1 request.
4. Python urllib and Node fetch do not retry
$ python3 -c "import urllib.request
urllib.request.urlopen('http://127.0.0.1:8990/')"
HTTPError 503 (Retry-After header: 2)
# server saw 1 hit
$ node -e "fetch('http://127.0.0.1:8990/').then(r => console.log(r.status, r.ok, r.headers.get('retry-after')))"
503 false 2
# server saw 1 hit
urllib raises HTTPError. Node fetch does not throw; it gives you ok: false. Neither one retries. If you want retries, write the loop yourself and read Retry-After.
5. Load shedding: 503 when too busy
Our second server allowed 5 requests in flight. Each took 500 ms. Request number 6 and up got 503 straight away.
let inflight = 0; const MAX = 5;
http.createServer((req, res) => {
if (inflight >= MAX) {
res.writeHead(503, {'Retry-After': '1', 'Connection': 'close'});
return res.end('overloaded\n');
}
inflight++;
setTimeout(() => { inflight--; res.end('ok\n'); }, 500);
}).listen(8991);
| Test | 200 | 503 |
|---|---|---|
| 20 parallel curl requests | 5 | 15 |
| 50 parallel curl requests | 5 | 45 |
| 3 requests one after another | 3 | 0 |
Exactly the limit got through. Everything else failed fast instead of piling up. This is better than letting requests queue until they time out (see 408 Request Timeout). We sent Connection: close on the 503 so busy clients do not hold a socket open; more on that in Keep-Alive and Connection.
6. Common causes (from the docs, not measured)
We did not test these. They come from vendor docs.
| Source | Why you see 503 |
|---|---|
| Planned maintenance | Your app or a maintenance page returns it on purpose. |
nginx limit_req / limit_conn | Requests over the limit get 503 by default. Change it with limit_req_status 429;. |
| Kubernetes / ingress | The Service has no ready endpoints, for example all pods fail their readiness probe. |
| IIS | The application pool is stopped or keeps crashing (rapid-fail protection). |
| Load balancers / CDNs | No healthy backend left in the pool. |
| App servers | Worker pool or connection pool exhausted. |
7. SEO impact (from the docs, not measured)
Google's docs say 503 is the right status for planned downtime. It tells Googlebot the outage is temporary, and crawling slows down. Do not serve the maintenance page with 200 (it can get indexed) or 404 (pages can drop out).
Google also says that if 503 lasts a long time (more than about two days), it starts treating the URLs like errors and may drop them from the index. Keep maintenance short. Return 503 for robots.txt too only if you want crawling to pause; a long 503 on robots.txt can stop crawling of the whole site. If you also want to keep a page out of search, use X-Robots-Tag, not 503.
8. Fixes
- Planned work: return 503 with
Retry-AfterandCache-Control: no-store. Keep the health check route outside the switch. - Rate limits: if the client is sending too much, 429 is clearer than 503. In nginx set
limit_req_status 429;. - Overload: add capacity or raise worker limits, but keep a load-shedding limit so you fail fast.
- Kubernetes: run
kubectl get endpointsand check readiness probes and pod logs. - IIS: start the app pool and read the Windows event log for why it stopped.
- Clients: retry with back-off and honour
Retry-After. curl does this with--retry; urllib and fetch need your own code.
Frequently asked questions
Does curl retry on a 503?
Only with --retry. In our test, --retry 3 made 4 requests in total and waited 2, 2 and 4 seconds, so it honoured Retry-After: 2. Without --retry it sent one request.
Do Python urllib and Node fetch retry 503 automatically?
No. Each sent exactly one request in our test. urllib raised HTTPError 503; fetch returned ok: false with status 503. You must write the retry loop yourself.
Should I use 503 or 429 for rate limiting?
Use 429 when one client is sending too many requests. Use 503 when the whole server is overloaded or down for maintenance. nginx limit_req returns 503 by default; change it with limit_req_status 429.
Will a 503 hurt my Google rankings?
From Google's docs, not measured: a short 503 with Retry-After is the right way to do maintenance and should not hurt. If it lasts for days, Google may treat the pages as errors and drop them.
What Retry-After value should I send?
A realistic number of seconds until you expect to be back, e.g. 120 for a short deploy. Clients like curl wait at least that long before retrying.
Written with AI assistance. Measured on 2026-10-02 with curl 8.14.1, Python 3.13 urllib and Node.js 24.21.0: a small Node server on 127.0.0.1:8990 returned 503 for maintenance and counted every hit, and a second server on 127.0.0.1:8991 shed load with 503 above 5 in-flight requests. PlainHeaders is our extension; curl, Python and Node.js are free.