pingrobot

Blog / Errors & fixes

HTTP 503 Service Unavailable: Overload, Maintenance, Retry-After

· 5 min read

Short answer

HTTP 503 Service Unavailable means the server is temporarily unable to handle the request, usually because it is overloaded or down for maintenance, and expects to recover. It can come from the app, a reverse proxy or a load balancer, and it may carry a Retry-After header saying when to try again. A monitor should still record a 503 as unavailable, even when the maintenance was planned.

An HTTP 503 Service Unavailable response means the server is currently unable to handle the request because of a temporary overload or scheduled maintenance, and the condition is expected to clear after some delay. That is how RFC 9110 (section 15.6.4) defines it. Unlike a 500, which signals an unexpected failure, a 503 is the server saying "not right now": it knows it cannot serve you and expects to recover.

What 503 means, precisely

RFC 9110 adds two details worth knowing:

  • The server may send a Retry-After header to suggest how long the client should wait before retrying.
  • A 503 does not imply that a server must use it whenever it is overloaded. Some servers simply refuse the connection instead, which a client sees as a connection error, not a status code.

So a 503 is a deliberate, informative response. It is the right code when you know the outage is temporary.

Who returns a 503

  • Your application, when it detects it cannot serve requests: a maintenance flag is set, a required dependency is down, or a health check fails. Laravel's php artisan down and similar maintenance modes in other frameworks return 503.
  • Reverse proxies. nginx's limit_req and limit_conn modules return 503 by default when a client exceeds the configured rate or connection count (limit_req_status and limit_conn_status change this). HAProxy returns 503 when no backend server is available.
  • Load balancers and orchestrators. A load balancer with no healthy targets, or a Kubernetes ingress with no ready endpoints behind a Service, typically answers 503.
  • Hosting platforms and CDNs, when your plan's resource limits are exceeded, the app is sleeping or starting, or the platform itself is in maintenance.

Common causes

  1. Planned maintenance – a deploy, a database migration or a hosting provider window.
  2. Traffic overload – a spike, a bot crawl or a campaign sends more requests than workers can handle, and the queue overflows.
  3. No healthy backends – every instance failed its health check, often after a bad deploy or a dependency outage.
  4. Rate limiting – a client, or a monitor checking too often from one IP, hits a proxy limit configured to answer 503.
  5. Resource limits on shared hosting – CPU, memory or process limits for the account are reached.
  6. App starting up – some platforms return 503 while a container or serverless instance is still booting.

The Retry-After header

Retry-After tells the client when it is reasonable to try again. RFC 9110 (section 10.2.3) allows two forms:

HTTP/1.1 503 Service Unavailable
Retry-After: 120
HTTP/1.1 503 Service Unavailable
Retry-After: Wed, 14 Oct 2026 18:30:00 GMT

The first is a number of seconds to wait; the second is an absolute HTTP date. Well-behaved clients, API SDKs and crawlers can use it to back off instead of hammering a server that is already struggling. Keep the value honest: a short estimate that you then miss produces a burst of retries at exactly the wrong time.

Maintenance mode done properly

A maintenance page should be served with status 503, not 200. A 200 tells browsers, crawlers and monitors that the page is healthy, and a cached 200 maintenance page can outlive the maintenance. A minimal nginx setup that switches maintenance on when a flag file exists:

server {
    # ...
    error_page 503 /maintenance.html;

    location / {
        if (-f /var/www/maintenance.flag) {
            return 503;
        }
        # your normal proxy_pass or try_files lines
    }

    location = /maintenance.html {
        root /var/www/static;
        add_header Retry-After 600 always;
        internal;
    }
}

Turn it on with touch /var/www/maintenance.flag and off by removing the file. The check sits inside location / so that the internal request for the maintenance page itself is not answered with another 503. The always parameter matters: without it, nginx only adds headers to successful responses, so Retry-After would be missing from the 503.

How to diagnose an unexpected 503

curl -sS -D - -o /dev/null https://example.com/

Look at the Server header, any Retry-After header and the body of the error page to identify the layer that generated it. Then:

  • nginx: sudo tail -n 50 /var/log/nginx/error.log. Lines containing limiting requests or limiting connections mean a rate or connection limit fired.
  • Load balancer: check the target group or backend health status in your cloud console, and the health check path.
  • Kubernetes: kubectl get endpoints <service> and kubectl get pods to see whether any pods are ready.
  • Application: check whether a maintenance flag was left on after a deploy. It happens more often than anyone admits.
  • Capacity: look at CPU, memory, worker counts and queue length at the time of the errors. In PHP-FPM, server reached pm.max_children setting in the FPM log means all workers were busy.

How to fix it

  • Remove a forgotten maintenance flag and make your deploy script turn maintenance off in a step that always runs, even on failure.
  • Add capacity or reduce cost per request. More workers or instances, caching for anonymous pages, and moving slow tasks to background jobs.
  • Tune rate limits. Set limits that real users do not hit, add a burst value, and consider returning 429 Too Many Requests, which is the more specific code for rate limiting (RFC 6585).
  • Fix health checks so that a healthy instance passes and an unhealthy one fails for the right reasons. A health check that depends on a non-critical service can take your whole site out.
  • Pre-warm on deploy so new instances are ready before old ones leave the pool.

How a monitor should treat a planned 503

This is where teams often disagree, so it helps to separate two questions.

Was the site available? No. During maintenance, users received 503 responses. An honest uptime record shows that period as unavailable, and your monitor should report it as down. A monitor that quietly ignores 503s will also ignore the unplanned ones caused by overload.

Was it a breach of your commitments? That depends on your agreement. Many SLAs exclude announced maintenance from the uptime calculation. That is an accounting decision you make when reporting, not something the monitor should hide by pretending the site was up.

Practical guidance:

  • Keep the monitor running during maintenance. It tells you the moment the site is back, which is exactly when you want to know.
  • Announce planned work in advance on a public status page, and post an update when it finishes, so customers do not report the 503 as an incident.
  • Keep maintenance short and send Retry-After, so clients and crawlers back off sensibly.
  • Investigate any 503 outside a planned window as an incident.

With PingRobot, an HTTP(S) monitor treats a 503 as a failure, re-checks from a second region, and alerts you by email; you can explain planned work to visitors on a public status page. For other 5xx codes, see 502 Bad Gateway and 504 Gateway Timeout, and to understand how downtime minutes add up, read uptime percentage explained.

Start monitoring free — 10 monitors, no credit card.

Frequently asked questions

How long does a 503 error last?

There is no fixed duration. It lasts until the overload clears or the maintenance ends. If the server sends a Retry-After header, that value is its own estimate of when to try again.

Is 503 the right status code for a maintenance page?

Yes. RFC 9110 names scheduled maintenance as a reason for 503. Serving the maintenance page with status 200 tells crawlers and monitors that the page is healthy, which it is not.

Does a 503 hurt search rankings?

Search engines generally treat a short 503 as a temporary condition and retry later, which is why it is preferred over 200 or 404 for maintenance. A 503 that lasts for days is a different matter, as crawlers may start treating the pages as unavailable.

Why does nginx return 503 when my app is fine?

nginx's request and connection limiting (limit_req and limit_conn) return 503 by default when a client exceeds the limit. Check the error log for "limiting requests" and consider setting limit_req_status 429 instead.