Status code 503: take a site down without losing rankings

·4 min read

Status code 503, Service Unavailable, tells a client the server can't handle the request right now because of overload or maintenance, and expects to recover. It is the right code for planned downtime, ideally sent with a Retry-After header. Google tolerates it for a day or two. Keep it up for weeks, or send it from robots.txt, and you start losing crawling and eventually indexed pages.

What does status code 503 mean?

RFC 9110 defines 503 as a temporary overload or scheduled maintenance "which will likely be alleviated after some delay." The key word is temporary. A 500 says something broke. A 503 says come back later.

The server may add Retry-After to say how long the outage should last. It takes either a number of seconds or an HTTP date:

HTTP/1.1 503 Service Unavailable
Retry-After: 3600
Content-Type: text/html; charset=utf-8

For how 503 compares with 500, 502 and 504, see our post on status code 500. The full list lives in our HTTP status codes guide.

How long can a site return 503 before Google drops pages?

Google's guide to pausing a site says to return 503 with an informational page if you need to take the site down for one or two days. It calls a few days the upper limit and asks for a best-effort date or duration in Retry-After.

Here is what happens while the 503 is live, per that guide and Google's HTTP status code documentation:

  • Google slows crawling of the site, in proportion to how many URLs return errors.
  • It ignores whatever content comes with the 503.
  • It can't refresh titles, descriptions or structured data, so Search keeps showing the old versions.
  • Indexed URLs stay indexed at first. URLs that keep returning a server error are eventually removed.

Google publishes no exact cutoff for "eventually". In practice, plan on hours, accept a day or two, and treat anything past a few days as risky. For a closure of a week or more, Google recommends a home page that returns 200 and explains the situation. Its guide also warns that ramping back up after a long closure is much harder.

Never return 503 for robots.txt

A 503 on robots.txt stops Google from crawling your entire site, including pages that work fine. Google's pause guide says so directly. The robots.txt specification explains the mechanics. For 12 hours Google crawls nothing and keeps retrying the file, and a 503 there "results in fairly frequent retrying." After that it falls back to the last good copy for up to 30 days.

The mistake is easy to make. A blanket maintenance rule catches every path, and robots.txt is just another path. So exempt it explicitly, and serve a static copy rather than one your app generates, because the app is what you took down.

How to set up a 503 maintenance page

Both setups send 503 with Retry-After and leave robots.txt alone.

Nginx

Touch a flag file to switch maintenance on and delete it to switch it off, with no config reload:

server {
    # ... listen, server_name, root ...

    set $maintenance 0;
    if (-f /etc/nginx/maintenance.on) { set $maintenance 1; }
    if ($uri = /robots.txt)           { set $maintenance 0; }
    if ($uri = /maintenance.html)     { set $maintenance 0; }
    if ($maintenance) { return 503; }

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

The always matters. Nginx's add_header only adds headers to 2xx and 3xx responses unless you pass it, so without it your 503 goes out with no Retry-After.

Cloudflare

A Worker on your site's route can answer every request at the edge while the origin is down:

const PAGE = `<!doctype html><title>Back soon</title>
<h1>Down for maintenance</h1><p>We expect to be back by 14:00 UTC.</p>`;

export default {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === "/robots.txt") return fetch(request);
    return new Response(PAGE, {
      status: 503,
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        "Retry-After": "3600",
        "Cache-Control": "no-store",
      },
    });
  },
};

fetch(request) passes robots.txt through to the origin, so keep that file served statically. If the origin is fully off, return the robots.txt text from the Worker instead.

For unplanned errors, Cloudflare's Custom Error Rules can replace an origin 500 with your own page and change the status to 503. Its older Error Pages feature does not apply to 503 responses, so use rules.

Common causes of an unplanned 503

If nobody planned maintenance, something in the stack is shedding load or has nothing to send traffic to. These are the usual suspects:

  • Rate limiting. Nginx's limit_req rejects excess requests with 503 by default, per the module docs. A burst from a crawler or a tight zone looks like an outage. Set limit_req_status 429 so the code says what happened.
  • No healthy backends. An AWS Application Load Balancer returns 503 when the target group has no registered targets. A deploy that deregisters every instance at once does this.
  • A maintenance mode left on. CMS updates that put up a 503 page and never take it down are common. Check the response on a URL the update didn't touch.
  • Real overload. A traffic spike or a slow database that exhausts workers. The 503 is the honest answer, but fix capacity before it lasts.

Check the access log for which user agents hit the 503s. If Googlebot is the one being rate-limited, raise its limits or Google will see a site that is always half down. If pages fell out of Search during a long outage, check whether each page is still indexed.

Check which URLs return 503

Our HTTP Status Bulk Checker requests up to 20 URLs from a pasted list, follows up to five redirects and gives each URL eight seconds to answer. It returns each URL's final status code, its first redirect and target, the hop count and the response time, plus totals for server errors, client errors and redirects.

It sends one request per URL and does not read page bodies, crawl your site or expand a sitemap. It can't see what Googlebot got, so a 503 that only fires under load still needs your logs. Run it with robots.txt in the list, before and after maintenance. A run costs 20 credits.

Keep reading