Home  /  CDN Architecture  /  CDN Stale Content Serving
CDN Architecture

CDN Stale Content Serving: stale-while-revalidate and stale-if-error Patterns

By FlyFish NWA Editorial, Network Security Engineer September 7, 2026 9 min read

Every CDN cache has an expiration moment where fresh becomes stale. Traditionally, that moment triggers a synchronous origin fetch that blocks the client. With stale-while-revalidate and stale-if-error, you change that contract: the edge can serve stale immediately while refreshing in the background, or serve stale when the origin is failing. Implemented correctly, these two directives cut tail latency by 60-90% and turn origin outages from hard downtime into invisible blips.

This guide explains how both patterns work at the HTTP and edge level, when to use them, and how to configure them safely without amplifying cache invalidation errors or serving poisoned content.

Core idea: Freshness is a spectrum, not a binary. For most assets, a 30-second-old response delivered in 15ms is more valuable than a perfectly fresh response delivered in 600ms after an origin round-trip.

Why Serving Stale Beats Serving Nothing

Without stale directives, a cache hit lifecycle is simple: HIT if age < max-age, otherwise MISS and block. That blocking revalidation creates two problems at scale. First, thundering herds: when a popular object expires, thousands of edge requests serialize behind one origin fetch, spiking TTFB and origin load. Second, fragility: any 5xx or timeout during revalidation becomes a user-visible error, even though a perfectly usable previous version sits on disk.

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages

Stale serving decouples delivery from validation. The edge answers from cache instantly, then reconciles with the origin asynchronously. For a deeper look at how TTLs and validation interact, see our CDN fundamentals guide and architecture overview.

Both directives were standardized in RFC 5861 and are now supported natively by Cloudflare, Fastly, Akamai, CloudFront, and modern browsers. They are not a hack; they are part of Cache-Control and CDN-Cache-Control semantics.

stale-while-revalidate: Hide Revalidation Latency

stale-while-revalidate (SWR) tells a cache: after max-age expires, you may serve the stale object for N additional seconds while you revalidate in the background. The client gets an instant stale response; the next client gets the fresh one.

How the state machine works

Consider Cache-Control: public, max-age=60, stale-while-revalidate=300:

The latency win is immediate. In production traces across edge networks, SWR reduces p95 TTFB for HTML from 250-400ms to 25-50ms during the revalidation window because no client waits for origin.

# Origin response headers
Cache-Control: public, max-age=60, stale-while-revalidate=300
CDN-Cache-Control: public, max-age=300, stale-while-revalidate=86400
ETag: "a3f8-20260907"
Surrogate-Key: product-4821 price-feed

Note the split: browsers get a short max-age for freshness, while the CDN gets a longer one via CDN-Cache-Control (or Surrogate-Control on Fastly). This layered approach is critical for Core Web Vitals — you keep LCP fast at the edge without forcing browsers to hold stale HTML too long.

When to use SWR

SWR is ideal where slightly stale is acceptable and revalidation cost is high: SSR HTML, API responses for feeds and listings, product catalog pages, and image optimization manifests. It is less appropriate for personalized checkout, auth tokens, or inventory counts where staleness equals oversell.

Set the SWR window proportional to your origin update frequency and revalidation latency. A common pattern is max-age at 10-20% of SWR. For a news homepage updated every 60s, max-age=10, stale-while-revalidate=60 keeps 90% of requests instant while guaranteeing refresh within a minute.

CDN Stale Serving Request Flow Flow showing fresh hit, stale-while-revalidate, and stale-if-error paths CLIENT EDGE CACHE Age / max-age SWR / SIE check ORIGIN request FRESH HIT age < max-age → serve instantly (HIT) STALE-WHILE-REVALIDATE → serve stale + async revalidate background If-None-Match STALE-IF-ERROR 5xx / timeout → serve stale instead of error origin 500 / 502 / 504 response
Request flow: fresh hits never contact origin; SWR serves stale synchronously and revalidates asynchronously; SIE serves stale only when origin returns 5xx or times out.

stale-if-error: Survive Origin Failures

stale-if-error (SIE) is your circuit breaker. It says: if revalidation fails with an error (500, 502, 503, 504, or network failure), you may serve stale for N additional seconds rather than propagating the error.

Example: Cache-Control: public, max-age=60, stale-while-revalidate=300, stale-if-error=86400 means that even if the object is 12 hours stale, the edge will still serve it during an origin outage rather than returning a 500 to users. For many content sites, a 12-hour-old article is infinitely better than an error page.

SIE does not trigger on 4xx. A 404 or 403 is considered authoritative — the origin is saying the resource does not exist or is forbidden, so the cache should not mask it. Only 5xx and timeouts qualify. Some CDNs let you extend this to include 421, 429, or specific timeout thresholds; verify your provider's mapping.

# Cloudflare / Fastly / Akamai behavior
if (origin_status in [500,502,503,504] or origin_timeout) {
  if (age < max-age + stale-if-error) return STALE;
  else return ERROR;
}

Operationally, SIE turns a hard dependency on origin availability into a soft one. Teams that set stale-if-error=86400 to 604800 (1-7 days) for static assets and SSR shells routinely ride through 10-30 minute origin deploys, database failovers, and regional brownouts with zero user impact. Pair it with origin shielding and health checks so stale is a last resort, not a crutch for an unhealthy origin.

Directive Trigger Serves Stale? Typical Window Use Case
max-age Age < max-age No — fresh only 10s – 5m (HTML), 1h – 1y (assets) Baseline freshness
stale-while-revalidate max-age expired, origin reachable Yes + async refresh 60s – 1 day Hide revalidation latency
stale-if-error Revalidation returns 5xx / timeout Yes instead of error 1h – 7 days Resilience during outages
must-revalidate / no-cache Expired + no SWR/SIE No — must block — Strict freshness (checkout, auth)

Implementation Across CDNs and Origins

Origin headers are the source of truth, but every CDN has an override layer. Configure both so behavior is predictable if headers are stripped.

1. Origin headers (recommended default)

// Node / Express
res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=300, stale-if-error=86400');
res.setHeader('CDN-Cache-Control', 'public, max-age=300, stale-while-revalidate=86400, stale-if-error=604800');

# Nginx
add_header Cache-Control "public, max-age=60, stale-while-revalidate=300, stale-if-error=86400" always;
add_header CDN-Cache-Control "public, max-age=300, stale-while-revalidate=86400, stale-if-error=604800" always;

# Varnish VCL
sub vcl_backend_response {
  set beresp.ttl = 60s;
  set beresp.grace = 24h; # maps to stale-if-error
  set beresp.keep = 7d;
}

2. CDN overrides

Always emit ETag or Last-Modified so background revalidations can be conditional (304). Without validators, every SWR triggers a full 200 re-download, erasing bandwidth savings.

3. Browser vs edge separation

Use CDN-Cache-Control to give edges a longer SWR window than browsers. Browsers should not hold stale HTML for hours. Edges can. This avoids the classic mistake of setting stale-while-revalidate=86400 globally and then wondering why users see yesterday’s price after a hard refresh bypasses the CDN but not the browser cache.

Tuning, Risks, and Cache Poisoning Defense

Stale serving is powerful but amplifies mistakes. A poisoned object cached once can be served stale for days via SIE.

When configured with layered TTLs, conditional revalidation, and proper cache keys, stale-while-revalidate and stale-if-error shift your CDN from a simple cache into a resilience tier — one that absorbs latency and outages so your origin does not have to be perfect to deliver a perfect user experience.

Frequently Asked Questions

What is the difference between stale-while-revalidate and stale-if-error?

stale-while-revalidate serves stale while the origin is healthy to hide revalidation latency — the edge refreshes in the background. stale-if-error serves stale only when revalidation fails with a 5xx or timeout, acting as a failover to keep the site online during outages. Most production configs use both together.

How long should stale-while-revalidate and stale-if-error be?

For HTML/SSR, start with max-age=60, stale-while-revalidate=300, stale-if-error=86400 and longer at the edge via CDN-Cache-Control: max-age=300, stale-while-revalidate=86400, stale-if-error=604800. For immutable assets (hashed JS/CSS/images), use max-age=31536000, immutable and skip stale directives. Adjust based on how stale your content can safely be.

Does stale-while-revalidate work in browsers?

Yes. Modern browsers honor stale-while-revalidate for HTTP cache, but support is best at CDNs and service workers. Use CDN-Cache-Control to give CDNs a longer SWR window than browsers, so users do not see overly stale HTML after a local refresh.

Can stale serving cause cache poisoning to persist longer?

Yes, if you cache an incorrect response and allow long SIE windows, the poisoned object will be served stale for the full window. Mitigate by never caching 5xx, requiring ETag/Last-Modified validators, scoping cache keys correctly with Vary, and ensuring purges clear both fresh and stale copies. Monitor Cache-Status headers for unexpected stale hits.

References & Standards: Cloudflare documentation · RFC 7234