stale-if-error

Caching

stale-if-error is a Cache-Control extension directive (RFC 5861) that lets a cache serve an already-stored stale response for a set number of seconds past expiry when the fetch to the origin fails. It trades slightly old content for an error page during an origin outage.

Also known as SIE.

13 min read Updated Aug 30, 2026

Full Explanation

stale-if-error is an extension directive of the Cache-Control header. It is defined in RFC 5861 section 4. It is listed in IANA's HTTP cache directive registry. It states that "when an error is encountered, a cached stale response MAY be used to satisfy the request, regardless of other freshness information". Its value is the number of seconds of staleness that stay acceptable. In practice, this covers two cases. The origin may be unreachable. Or it may answer with a server error. Either way, a cache may already hold an expired copy. Such a cache may hand that stale content back to the client instead of an error page.

It is not stale-while-revalidate. That sibling directive comes from the same RFC. It hides latency while the origin is healthy. It answers from cache and refreshes in the background. stale-if-error fires only when a fetch fails. It does not spare the request from waiting for that failure first. It is not a general permission to serve stale content either: the client-side equivalent is the max-stale request directive. It is also not a cache-fill mechanism. With nothing stored, there is nothing stale to serve. So the user gets the real error. Nor is it a blanket rule for any 5xx. RFC 5861's error set is exactly 500, 502, 503 and 504. The CDNs that implement it disagree with each other about which failures count. So the vendor detail below matters as much as the RFC.

How it works

The origin sets the directive. It normally sits next to max-age. RFC 5861 gives the syntax as stale-if-error = "stale-if-error" "=" delta-seconds. So the value is a whole number of seconds:

Cache-Control: public, max-age=300, stale-if-error=86400
  1. While the response is fresh, nothing changes. For the first 300 seconds the cache serves ordinary hits. The directive is dormant metadata.
  2. Once the copy is stale, the cache still goes to the origin. The next request blocks on that fetch. Fastly states the contrast with the sibling directive plainly: "Unlike SWR, SIE doesn't allow for any asynchronous revalidation" (Fastly, freshness, staleness and revalidation). stale-if-error buys availability, not speed.
  3. If that attempt fails, the stored copy may be served instead of the failure. RFC 5861 defines the trigger as "any situation that would result in a 500, 502, 503, or 504 HTTP response status code being returned". Its introduction also covers cases where no answer arrives at all. It names those cases as "a 500 Internal Server Error, a network segment, or DNS failure".
  4. The value caps staleness. It does not extend freshness. "Its value indicates the upper limit to staleness; when the cached response is more stale than the indicated amount, the cached response SHOULD NOT be used to satisfy the request, absent other information." In the RFC's worked example, max-age=600 pairs with stale-if-error=1200. Together they cover errors until the copy's age reaches 1800 seconds. After that, "the cache must write the error message through". The clock runs on the object. Repeated failures never renew the window. Each fallback response is older than the last.
  5. The fallback goes out visibly stale. RFC 9111 requires that "when a stored response is used to satisfy a request without validation, a cache MUST generate an Age header field" (RFC 9111 section 4). So a non-zero Age header is how you spot it. RFC 5861 also asked for a warning header. But RFC 9111 section 5.5 obsoletes Warning "as it is not widely generated or surfaced to users". It points at Age instead.

A client may send the directive too. RFC 5861 states: "when used as a request Cache-Control extension, its scope of application is the request it appears in; when used as a response Cache-Control extension, its scope is any request applicable to the cached response in which it occurs." In CDN work, the response direction is the one that matters, because the edge cache is the thing being instructed.

The directive exists because the current caching standard forbids stale responses by default. That standard then names the directive as one of the exceptions. RFC 9111 section 4.2.4 reads: "A cache MUST NOT generate a stale response unless it is disconnected or doing so is explicitly permitted by the client or origin server (e.g., by the max-stale request directive in Section 5.2.1, extension directives such as those defined in [RFC5861], or configuration in accordance with an out-of-band contract)." RFC 9111 also supplies the failure hook. On a 5xx received while validating, a cache "can either forward this response to the requesting client or act as if the server failed to respond". In the second case, it may send a previously stored response (section 4.3.3). RFC 5861 itself is an Informational, independently submitted RFC from May 2010. It was written against the long-obsolete RFC 2616. RFC 9111 (STD 98, June 2022) is the caching standard used today. The directive depends on it.

Why it matters for a CDN

A CDN edge server is the only thing between the audience and the origin. So an origin failure is a total outage, unless the edge keeps answering from what it already holds. stale-if-error turns that outage into degraded freshness for everything already cached. That is the RFC's stated purpose. A cache returns the stale response instead of a hard error. As RFC 5861 section 1 puts it, "This improves availability". It is also the cheapest resilience control on offer. That is because it is one token in an origin response header. There is no dashboard, no failover config, and no surcharge on the platforms that honour it.

Two limits keep it honest. First, it only covers URLs with a stored copy. A cold cache, a purged object or an uncacheable route still returns the real error. CloudFront's documentation puts that bluntly. With neither stale-if-error nor custom error responses configured, it "will return the stale object or forward the error response back to viewer, depending on whether the requested object is in the edge cache or not". Second, it masks an outage rather than surviving one. So it complements origin shield, health-checked origin pools and real origin capacity. It does not replace them.

What CDNs do

Cloudflare

  • Whether origin Cache-Control is honoured at all depends on the Origin Cache Control feature. "Free, Pro and Business customers have this feature enabled by default". Enterprise zones can turn it off (Cloudflare, Origin Cache Control).
  • The trigger set is fixed and narrow. Cloudflare states: "The stale-if-error directive triggers when your origin returns a 5xx status code (500, 502, 503, 504). Other responses such as 404 are not considered errors for this purpose". The same page adds: "There is currently no way to customize which status codes trigger stale-if-error."
  • It is ignored "if Always Online is enabled or if an explicit in-protocol directive is passed". Those directives are no-store, no-cache, must-revalidate, s-maxage or proxy-revalidate.
  • The behaviour is opt-out rather than opt-in. To suppress it, "include stale-if-error=0 directive with the object returned from the origin". The directive is also unavailable through the Workers Cache API methods cache.match and cache.put.

Amazon CloudFront

  • CloudFront has a wider trigger than Cloudflare. It serves stale "if the origin is unreachable or returns an error code that is between 500 and 600" (CloudFront Developer Guide, Serve stale (expired) content).
  • The window is capped twice. CloudFront states: "CloudFront will serve the stale content up to the value of the stale-if-error directive or the value of the CloudFront maximum TTL, whichever is less." A 24-hour directive behind a one-hour maximum TTL buys one hour.
  • Custom error responses come second. CloudFront attempts the stale copy first. It serves the configured error page only when no stale copy is available.
  • Support landed in May 2023 and is platform-wide. "Support for these directives is now available in all CloudFront edge locations, at no additional cost" (AWS announcement, 17 May 2023).

Fastly

  • The value is parsed from Surrogate-Control or Cache-Control into beresp.stale_if_error. It is clamped per request by req.max_stale_if_error. So a request-level ceiling can shorten an object's window (beresp.stale_if_error).
  • Only a failing health check serves stale on its own. If the origin is sick, "the stale content will be served automatically". A valid 5xx sets stale.exists in vcl_fetch, and an unreachable origin sets it in vcl_error. Delivering the stale copy then needs an explicit return(deliver_stale). The default for a valid 5xx is to serve the origin's error. If the response is cacheable, it then stores that error over the good object.
  • The two stale windows can overlap. During that overlap, "SWR takes precedence" while it lasts. "SIE acts as a fallback" once it closes. Both windows start when max-age expires. So they run simultaneously rather than end to end.

nginx

  • nginx has read both RFC 5861 extensions from the upstream Cache-Control header since 1.11.10 (February 2017). The header form "has lower priority than using the directive parameters" of proxy_cache_use_stale (nginx proxy module docs).
  • Since 1.19.3 (September 2020) upstream status codes no longer trigger it at all. The changelog files the old behaviour as a bug. It says the extension "was erroneously applied if backend returned a response with status code 500, 502, 503, 504, 403, 404, or 429" (nginx CHANGES). The commit message is explicit. It says: "Now stale-if-error only works for network/timeout/format errors and ignores the upstream HTTP code. The return of a stale response for certain HTTP codes is still possible using the proxy_cache_use_stale directive" (nginx-devel, 14 August 2020). On current nginx you must list http_500 and friends in proxy_cache_use_stale to survive an origin 5xx.

Varnish

  • The Varnish users guide documents grace and keep rather than the RFC 5861 directives. Grace is the stale-while-revalidate shape. The guide explains: "Setting an object's grace to a positive value tells Varnish that it should serve the object to clients for some time after the TTL has expired, while Varnish fetches a new version of the object" (Grace mode and keep).
  • The stale-if-error shape comes from gating grace on backend health. Set a long beresp.grace. Then cap req.grace to a few seconds while std.healthy(req.backend_hint) is true. That way the long window applies only when the backend is sick. The same page also suggests abandoning a background fetch that returns 5xx. That way an error response never replaces the good object.

Here is that gating in VCL:

import std;

sub vcl_backend_response {
    set beresp.grace = 24h;
}

sub vcl_recv {
    if (std.healthy(req.backend_hint)) {
        // healthy backend: cap the grace window to 10s
        set req.grace = 10s;
    }
    // sick backend: no cap, so the full 24h grace applies
}

Watch out for

  • "Serve stale on 5xx" is not portable. Cloudflare fires on four codes. CloudFront fires on anything from 500 to 600. Current nginx fires on no HTTP status code at all. Fastly needs edge code when the 5xx arrives as a valid response. Verify on the platform you actually run.
  • Stricter directives win. "A cache MUST NOT generate a stale response if it is prohibited by an explicit in-protocol directive (e.g., by a no-cache response directive, a must-revalidate response directive, or an applicable s-maxage or proxy-revalidate response directive)" (RFC 9111 section 4.2.4). s-maxage carries proxy-revalidate semantics. So s-maxage, or must-revalidate, on the same response normally disables the fallback. Cloudflare documents exactly that behaviour.
  • It does not rescue a 4xx. A 404 or 403 is not an error for this purpose. A full response tells the cache "that none of the stored responses nominated in the conditional request are suitable". The cache MUST use it to satisfy the request. It MAY also store it (RFC 9111 section 4.3.3). Cloudflare says outright that "the origin response replaces the stale cached content". An origin can start returning 404 under load. That poisons the cache instead of falling back.
  • The window is a countdown on the object, not a lease. Hard errors return the moment staleness passes the value. The copy served ages with every failure. Nothing refreshes it until the origin recovers.
  • It costs latency during the outage. Every request still attempts the origin. It waits for the failure before the stale copy goes out. So a slow-failing origin turns into slow 200s. Origin connect and read timeouts are what bound that wait.
  • Caps and switches are silent. CloudFront's maximum TTL truncates the window. Fastly's req.max_stale_if_error truncates it too. Cloudflare drops the directive entirely when Always Online is enabled.
  • Nothing stored means nothing to fall back on. Authenticated and private responses are not in a shared cache to begin with. RFC 9111 explains: "A shared cache MUST NOT use a cached response to a request with an Authorization header field (Section 11.6.2 of [HTTP]) to satisfy any subsequent request unless the response contains a Cache-Control field with a response directive (Section 5.2.2) that allows it to be stored by a shared cache" (RFC 9111 section 3.5). And no-store forbids storage outright.

Best practice

  • Choose a window the audience genuinely tolerates. State it deliberately. max-age=300 with stale-if-error=86400 means five minutes fresh. It also means up to a day of degraded freshness during an outage. Keep it short for prices, stock levels and anything with legal exposure. Long windows suit documentation, marketing pages and static assets.
  • Pair it with stale-while-revalidate instead of choosing between them. One hides normal-path latency. The other covers failure. Remember Fastly's documented ordering. The stale-while-revalidate window wins while it is open. stale-if-error takes over after it closes.
  • Keep s-maxage, must-revalidate and no-cache off any response you want the fallback for. When edge and browser lifetimes must differ, use the CDN's own edge TTL control rather than s-maxage.
  • Check the effective ceiling on the platform, not just the header. CloudFront clamps to the maximum TTL. Fastly clamps to req.max_stale_if_error. Cloudflare ignores the directive under Always Online.
  • Send stale-if-error=0 where a stale answer is worse than an error, such as checkout totals, balances or one-time tokens. Otherwise, CloudFront serves the previously fetched object when the origin is unreachable. This applies as long as the minimum or maximum TTL is above zero. Cloudflare documents the same opt-out.
  • Test the failure path before you need it. Make the origin return 503, then drop connections entirely. Confirm the edge answers 200 with a non-zero Age instead of an error. Confirm too that an uncached URL still errors. That way, nobody mistakes the directive for redundancy.

Examples

This is a resilient header for a content site:

Cache-Control: public, max-age=300, stale-if-error=86400

Content is fresh for 5 minutes. If the origin fails, the cache serves the stale version for up to 24 hours. Add stale-while-revalidate for the best of both worlds:

Cache-Control: public, max-age=300, stale-while-revalidate=60, stale-if-error=86400

This gives fast responses in normal use and resilience during outages.

Frequently Asked Questions

stale-if-error is a Cache-Control extension directive (RFC 5861) that lets a cache serve an already-stored stale response for a set number of seconds past expiry when the fetch to the origin fails. It trades slightly old content for an error page during an origin outage.

This is a resilient header for a content site:

Cache-Control: public, max-age=300, stale-if-error=86400

Content is fresh for 5 minutes. If the origin fails, the cache serves the stale version for up to 24 hours. Add stale-while-revalidate for the best of both worlds:

Cache-Control: public, max-age=300, stale-while-revalidate=60, stale-if-error=86400

This gives fast responses in normal use and resilience during outages.

Yes. stale-if-error is also known as SIE. stale-if-error is a Cache-Control extension directive (RFC 5861) that lets a cache serve an already-stored stale response for a set number of seconds past expiry when the fetch to the origin fails. It trades slightly old content for an error page during an origin outage.