Quick answer: A 503 Service Unavailable means the server is up but cannot handle the request right now, usually because it is overloaded, under maintenance, or missing a dependency. It is a temporary condition, so look for a Retry-After header and retry later with exponential backoff and jitter rather than hammering the endpoint.

What 503 actually signals

A 503 is a 5xx response about server availability, not a client error. The process is running and answering at the HTTP layer, but it cannot serve this request right now, which separates it from a crash and from rate limiting.

Code Meaning Who is at issue Typical fix
503 Temporarily unable to handle the request Server capacity or maintenance Back off, wait for Retry-After
429 Too many requests from this client Client send rate Slow down, honor Retry-After
500 Unexpected internal error Server code defect Fix the bug, not the retry logic
502 Bad response from upstream Upstream or gateway Check the upstream process

The 503 versus 429 distinction causes the most confusion. A 429 says you are sending too fast; a 503 says the service cannot take anyone's load right now. Both may carry Retry-After, but the trigger differs.

Why a 503 gets returned

Overload is the common case. When the worker pool, connection pool, or request queue fills, the server rejects new work instead of letting requests pile up and time out. CPU or memory pressure, a saturated database, or a traffic spike can push it there.

Planned maintenance and deploys return 503 on purpose so clients read the outage as temporary. A missing dependency can produce the same status: when the database or a downstream service is unreachable, well-behaved code returns 503 for a condition it expects rather than a 500. Load balancers return 503 when no healthy node remains, and an open circuit breaker emits one to fail fast.

Reading the Retry-After header

Retry-After states how long to wait, either as seconds or an HTTP date. A value of 30 means hold 30 seconds; a date means wait until that moment. If it is present, build your wait around it rather than your own schedule.

When the header is missing, the client picks its own delay. A fixed short interval across many clients synchronizes retries into a second wave exactly when the service is struggling.

Backing off the right way

Use exponential backoff: grow the wait each attempt, cap it at a maximum, and stop after a bounded number of tries. Add jitter so clients do not retry in lockstep.

Element Good practice Failure mode if skipped
Delay growth Exponential, with a ceiling Retries stay too frequent
Jitter Randomize each wait Retry storm from synchronized clients
Retry-After Obey it when present Ignoring the server's own estimate
Retry cap Fixed max attempts Infinite loops and extra load
Method check Retry only safe, idempotent calls Duplicate writes

Idempotency is the guardrail. Retrying a GET or a request with an idempotency key is safe; a plain POST that creates a charge or order can double the action. When in doubt, do not auto-retry the write.

Fixing it on the server

Backoff makes the client polite, but a recurring 503 points to a capacity problem the client cannot fix. Add workers or scale the saturated resource, and rate-limit at the edge so a 429 sheds traffic before the service degrades to 503. Add graceful degradation, tune pool and queue sizes to real limits, and make health checks remove bad nodes. During maintenance, serve 503 with a realistic Retry-After.

Verifying the retry logic offline

You can confirm the retry logic without touching a failing production service. A local mock can return 503 with Retry-After on the first call and a success response on the next, recording every request. A scenario can assert status and headers at each step and check that a non-idempotent write is not retried. Powerduck runs these requests through the local main process, so no browser CORS blocks the calls, and it keeps the mock traffic and assertions as evidence. For the rest of the series, see the API debugging guide hub.

Honest limits

A status code narrows the problem but does not name the root cause. The same 503 can reflect a full queue, a deploy, or a missing dependency, so logs and metrics stay the source of truth. A mock returning 503 tests client behavior; it cannot reproduce real overload, and a passing scenario proves the retry logic, not the production fix.

Frequently asked questions

Is a 503 the client's fault? No. It reports a server-side availability problem. A client can still make things worse by retrying too aggressively, which is why backoff matters.

How long should I wait before retrying? Use Retry-After if the server sends one. Otherwise start around one second and grow the wait with jitter each attempt, capped at a maximum and a fixed retry count.

Should I retry a POST on 503? Only if the operation is idempotent or carries an idempotency key. A plain POST that creates a resource can produce duplicates, so do not auto-retry it.

What is the difference between 503 and 429? 429 means the client is sending too many requests; 503 means the service cannot handle requests at all right now. Both can include Retry-After.

The bottom line

Read 503 as a temporary "not now," not a crash: check Retry-After, then retry with exponential backoff, jitter, a hard cap, and only for idempotent calls. On the server, treat repeated 503s as a capacity signal and shed load before the whole service stalls. Verify the client against a mock that returns the status and header, then fix the real capacity fault rather than relying on clients to wait forever.