---
title: Lifetime and revalidation
summary: null
url: https://www.fastly.com/documentation/guides/concepts/cache/stale
---

> **WARNING:** The before-send and after-send callbacks discussed on this page are part of *customized readthrough (HTTP) cache behavior*. For the Compute JavaScript and Go SDKs, this is an opt-in feature. See [this note](/guides/concepts/cache#customizing-cache-interaction-with-the-backend) for details.

## Freshness and Staleness

When content is stored in Fastly's cache, it has a freshness lifetime, also known as a **TTL** or "time to live". This is the period of time during which the cache will serve the content in response to a compatible request, without revalidating it with the origin server.

Once the freshness lifetime expires, the content will no longer be served directly and unconditionally from cache, but the expiry does not prompt the object to be deleted. Instead, it will be marked as **stale**. The way objects are treated once they transition to a stale state can be customized.

Objects may have one of three distinct types of stale state, in addition to being fresh. As an example, consider an object arriving from the backend with the following response header:

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

When processed by the [readthrough cache](https://www.fastly.com/documentation/guides/concepts/cache#readthrough-cache) this object will, unless evicted earlier due to lack of use, transit the following states:

![stale phases](/img/stale-phases-precomposed.png)

### Cdn Services

When a client request matches a stale object, the readthrough cache interface processes the object using the following steps:

1. If the backend is sick (i.e. is currently failing a [health check](https://www.fastly.com/documentation/guides/concepts/healthcheck)), and the object is within a `stale-while-revalidate` or `stale-if-error` period, then the stale object will be served.
2. Otherwise, if the object is within a `stale-while-revalidate` period, then it will be served immediately to the client. Additionally, an asynchronous fetch to the backend will be scheduled to [revalidate](https://www.fastly.com/documentation/guides/concepts/cache/stale#revalidation) or replace the stale object. See [background revalidation process flow](https://www.fastly.com/documentation/guides/concepts/cache/stale#background-revalidation-process-flow) for details on this process.
3. Otherwise, if the object is within a `stale-if-error` period and your VCL explicitly returns `deliver_stale` from `vcl_miss`, `vcl_fetch`, or `vcl_error`, the stale object will be served. See [explicitly serving stale content](https://www.fastly.com/documentation/guides/concepts/cache/stale#explicitly-serving-stale-content) for details.
4. Otherwise, the readthrough cache interface will perform a _blocking_ fetch to the backend to [revalidate](https://www.fastly.com/documentation/guides/concepts/cache/stale#revalidation) or replace the stale object, and then return the resulting object to the client.

The HTTP `Cache-Control` header directives [stale-while-revalidate](https://httpwg.org/specs/rfc5861.html#rfc.section.3) and [stale-if-error](https://httpwg.org/specs/rfc5861.html#rfc.section.4) (which trigger the scenarios above) are HTTP standards defined in [RFC 5861](https://httpwg.org/specs/rfc5861.html) - HTTP Cache-Control Extensions for Stale Content.

> **HINT:** Setting the `req.hash_always_miss` or `req.hash_ignore_busy` variables to `true` will disable the effects of the `Cache-Control` directives `stale-while-revalidate` and `stale-if-error` for that request.

> **WARNING:** Content is not guaranteed to be stored for the entire freshness lifetime and, especially in the case of large objects that are not frequently requested, may be evicted to make space for more popular objects.

> **HINT:** If your website were a radio station, then the CDN edge cache would be like your regional transmitter towers - essential to extend your reach to a huge audience, but useless without a signal to broadcast. Large state broadcasters have long realized this and placed local recordings of content at transmission sites "just in case" the transmitter loses its uplink to home base.
>
> Serving content from Fastly's platform to end users is very fast, and very reliable. But this only happens, by default, when that content is available (and fresh) in the cache. Use of `stale-while-revalidate`, `stale-if-error` and custom VCL that intercepts backend errors can significantly improve outcomes for end users.

### Compute Services

When a client request matches a stale object, the readthrough cache interface processes the object using the following steps:

1. If the object is within a `stale-while-revalidate` period, then it will be served immediately to the client. Additionally, an asynchronous fetch to the backend will be scheduled to [revalidate](https://www.fastly.com/documentation/guides/concepts/cache/stale#revalidation) or replace the stale object. See [background revalidation process flow](https://www.fastly.com/documentation/guides/concepts/cache/stale#background-revalidation-process-flow) for details on this process.
2. Otherwise, the readthrough cache interface will perform a _blocking_ fetch to the backend to [revalidate](https://www.fastly.com/documentation/guides/concepts/cache/stale#revalidation) or replace the stale object, and then return the resulting object to the client.

The HTTP `Cache-Control` header directive [stale-while-revalidate](https://httpwg.org/specs/rfc5861.html#rfc.section.3) (which triggers the scenario above) is an HTTP standard defined in [RFC 5861](https://httpwg.org/specs/rfc5861.html) - HTTP Cache-Control Extensions for Stale Content.

> **WARNING:** Content is not guaranteed to be stored for the entire freshness lifetime and, especially in the case of large objects that are not frequently requested, may be evicted to make space for more popular objects.

> **IMPORTANT:** In a Compute service, the readthrough cache interface does not currently support `stale-if-error`.

> **HINT:** If your website were a radio station, then the CDN edge cache would be like your regional transmitter towers - essential to extend your reach to a huge audience, but useless without a signal to broadcast. Large state broadcasters have long realized this and placed local recordings of content at transmission sites "just in case" the transmitter loses its uplink to home base.
>
> Serving content from Fastly's platform to end users is very fast, and very reliable. But this only happens, by default, when that content is available (and fresh) in the cache. Use of `stale-while-revalidate` can significantly improve outcomes for end users.

Staleness behaviors and revalidation are supported directly by the [readthrough cache interface](https://www.fastly.com/documentation/guides/concepts/cache#readthrough-cache) and can be explicitly configured using the low level [core cache interface](https://www.fastly.com/documentation/guides/concepts/cache#core-cache). The simple cache interface does not support staleness or revalidation.

## Revalidation

When a backend fetch is triggered by a cache object being stale, if the object has a **validator** (an `ETag` or `Last-Modified` header), the readthrough cache interface will make a _conditional_ GET request for the resource, by sending an `If-None-Match` and/or `If-Modified-Since` header as appropriate (if both validators are present, both headers are sent). If the stale object does not have a validator, the backend request will be a normal fetch to load the entire object.

If the readthrough cache interface makes a conditional request in this way, it will expect a backend response which may or may not have a status code of `304 Not Modified`. Depending on the response, the cache will behave as follows:

- **The backend response has a status code of `304 Not Modified`**

  If the response to a revalidation request has status `304 Not Modified`, this will cause the lifetime of the _existing object_ to be extended based on the [standard rules on calculating cache TTL](https://www.fastly.com/documentation/guides/concepts/cache/cache-freshness), and will reset the object's `Age`.

  > **NOTE:** If the initial object's TTL was determined by an `Expires` header and no freshness-related headers are present on a `304` response, the cache will set a TTL of _2 minutes_ (a default TTL) for the existing object. This is because the `Expires` header value identifies a fixed point in time while other freshness header values are given as times relative to the time that the response was received.

  ### Cdn Services

  No other aspects of the existing cached response will be modified. For example, this means that after successfully revalidating, future requests for the object will receive the headers that were attached to the original response from the backend that populated the cache, not the headers present on the revalidation response.

    This kind of response _does not trigger `vcl_fetch`_.

  ### Compute Services

  In a Compute service, the readthrough cache interface invokes the after-send callback, passing in a ["candidate" response object](https://www.fastly.com/documentation/guides/concepts/cache#candidate-response) that _represents a complete response_ except for the body. This object has the status code, response headers, and cache policy based on the existing cached response, and reflects any headers and cache policy updates included in the backend revalidation response (for example, to extend the object's lifetime). These values are further merged with any changes you make during the after-send callback, and then finally used to update the cached response object. As a revalidation response does not contain a body, these updates occur "in-place", _without invoking the body-transform callback_, and without changing the cached response body.

  

- **The backend response has a status code other than `304 Not Modified`**

  ### Cdn Services

  Any response to a revalidation request other than `304` will be processed normally, trigger `vcl_fetch`, and (if cacheable) will replace the stale object in cache.

  ### Compute Services

  In a Compute service, any response to a revalidation request other than `304` will be processed normally, trigger the after-send and body-transform callbacks, and (if cacheable) will replace the stale object in cache.

  

> **HINT:** Revalidations triggered as a result of a `stale-while-revalidate` directive happen in the background, _after_ the stale object has already been delivered to the client. See [Stale while revalidate](https://www.fastly.com/documentation/guides/concepts/cache/stale#stale-while-revalidate-eliminate-origin-latency) for details.

### Disabling revalidation

In some cases an origin server may be configured to serve responses with `ETag` or `Last-Modified` headers, but you prefer not to allow revalidation.

### Cdn Services

To disable revalidation entirely, remove the `ETag` and `Last-Modified` headers from the response when it's received from the backend. This can be done in `vcl_fetch`:

```vcl context="sub vcl_fetch { ... }"
unset beresp.http.etag;
unset beresp.http.last-modified;
```

Alternatively, you can enable revalidation between client and Fastly's cache, whilst effectively disabling it between Fastly's cache and the backend, by modifying the value of the `ETag` header:

```vcl context="sub vcl_fetch { ... }"
set beresp.http.etag = beresp.http.etag "-fastly";
unset beresp.http.last-modified;
```

### Compute Services

To disable revalidation entirely, remove the `ETag` and `Last-Modified` headers from the response when it's received by from the backend. This can be done in the [after-send](https://www.fastly.com/documentation/guides/concepts/cache#after-send-callback) callback:

### Rust

```rust compile_fail
req.set_after_send(|resp| {
  resp.remove_header("ETag");
  resp.remove_header("Last-Modified");
  Ok(())
});
```

### Javascript

```javascript
res = fetch(url, {
  cacheOverride: new CacheOverride({
    afterSend(resp) {
      resp.headers.delete('ETag');
      resp.headers.delete('Last-Modified');
    }
  })
});
```

### Go

```go
r.CacheOptions.AfterSend = func(cr *CandidateResponse) error {
  cr.DelHeader("ETag")
  cr.DelHeader("Last-Modified")
  return nil
}
```

### Cpp

In this version of the Fastly Compute C++ SDK, this behavior cannot be overridden.

Alternatively, you can enable revalidation between the client and Fastly's cache, whilst effectively disabling it between Fastly's cache and the backend, by modifying the value of the `ETag` header:

### Rust

```rust compile_fail
req.set_after_send(|resp| {
  if let Some(etag) = resp.get_header_str("ETag") {
    resp.set_header("ETag", format!("{etag}-fastly"));
  }
  resp.remove_header("Last-Modified");
  Ok(())
});
```

### Javascript

```javascript
res = fetch(url, {
  cacheOverride: new CacheOverride({
    afterSend(resp) {
      const etag = resp.headers.get('ETag');
      if (etag != null) {
        resp.headers.set('ETag', `${etag}-fastly`);
      }
      resp.headers.delete('Last-Modified');
    }
  })
});
```

### Go

```go
r.CacheOptions.AfterSend = func(cr *CandidateResponse) error {
  if _, err := cr.Header("ETag"); err == nil {
    cr.DelHeader("Last-Modified")
  }
  return nil
}
```

### Cpp

In this version of the Fastly Compute C++ SDK, this behavior cannot be overridden.

## Explicitly serving stale content

<div id="modifying-staleness-related-behavior-in-vcl-services"></div>

### Cdn Services

Fastly's cache honors staleness-related caching directives as indicated above, and in a VCL service, it is possible to use edge code to further control the serving of stale content.

- Stale content can be explicitly selected(scenario 3 above), if a cached object exists and is within a `stale-if-error` period, in the following subroutines:

  - **In `vcl_fetch`**: if the origin returns a response which is _valid_ HTTP, then Fastly's cache will by default serve the received object, and if [cacheable](https://www.fastly.com/documentation/reference/vcl/variables/backend-response/beresp-cacheable), use it to replace the stale object in cache. However, if the response is nonsensical or an error, you may prefer in that scenario to serve the stale content instead, by using `return(deliver_stale)`.
  - **In `vcl_error`**: if, during a fetch to origin, Fastly's cache encounters a _network level_ error, such as finding the origin unreachable or being unable to negotiate an acceptable TLS session, an error will be triggered and VCL control flow will be moved to `vcl_error` directly, without running `vcl_fetch`. By default, this will result in serving an error page generated by the Fastly platform to the end user, but if stale content exists in cache you can opt to use this instead by using `return(deliver_stale)` from `vcl_error`.
  - **In `vcl_miss`**: it is also possible to switch to a stale object in `vcl_miss`, but there is unlikely to be a reason to do so.

- The existence of stale content can be checked during the above subroutines using the `stale.exists` variable, which will only be `true` if a cached object exists and is within a `stale-if-error` period. Stale content in the _expired_ state cannot be used from VCL.

### Compute Services

In a Compute service, the readthrough cache interface does not support `stale-if-error`, and explicitly serving stale content is not supported.

## Stale while revalidate: Eliminate origin latency

`stale-while-revalidate` tells caches that they may continue to serve a response after it becomes stale for up to the specified number of seconds, provided that they work asynchronously in the background to fetch a new one. For example, an origin server may provide a response with the following headers:

```http
Cache-Control: max-age=300, stale-while-revalidate=60
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
```

Upon receiving this response, Fastly's cache will store and reuse that response for up to 5 minutes (`max-age=300`) as normal. Once that freshness lifetime expires, the `stale-while-revalidate` directive allows it to continue to serve the same content for up to another 60 seconds, provided that time is used to [revalidate](https://www.fastly.com/documentation/guides/concepts/cache/stale#revalidation) the cached content with the origin server in the background. As soon as a new response is available, it will replace the stale content and its own cache freshness rules will take effect. However, if the 60-second revalidation period expires, and it has not been possible to get the updated content, the stale version will no longer be usable in this manner (it may remain stale for a further period of time if it has an additional `stale-if-error` directive).

### Background revalidation process flow

When a cache lookup results in a stale object that is within a `stale-while-revalidate` period, the readthrough cache interface will _fork the request into two paths_. One path will serve the stale object to the current client, while the second will asynchronously fetch the object from the origin.

> **IMPORTANT:** If there is already a background revalidation in progress for the resource being requested, the stale object will be served but a new revalidation will not be triggered. See [request collapsing](https://www.fastly.com/documentation/guides/concepts/cache/request-collapsing).

### Cdn Services

In a VCL service, each stage invokes VCL subroutines as follows:

![Background revalidation flow](/img/stale-swr-flow-precomposed.png)

VCL subroutines executing in a background revalidation context are identifiable via the `req.is_background_fetch` VCL variable.

Background revalidations will only repopulate cache if the process above is successful and `beresp.cacheable` is set to `true` at the end of `vcl_fetch`. Otherwise, the stale object will continue to be used and background revalidation will continue to be attempted on subsequent requests, until the SWR period expires.

### Compute Services

In a Compute service, the stale object is returned immediately to the calling code, and the revalidation occurs at a later time (before the application instance is terminated).

If a background revalidation is successful, it _does not reset the `Age`_ of the object.

Background revalidations are eligible for [request collapsing](https://www.fastly.com/documentation/guides/concepts/cache/request-collapsing).

## Stale if error: Survive origin failure

`stale-if-error` is an instruction that if a backend is sick (i.e. is currently failing a [health check](https://www.fastly.com/documentation/guides/concepts/healthcheck)), a stale response may be used instead of outputting an error - which helps always guarantee a nice user experience even during periods of server instability.

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

In the above example, Fastly's cache will store and serve the fresh content for 5 minutes, just like the previous example, but this time, when the 5 minutes has expired, the next request for this content will _block on a synchronous fetch to origin_. Unlike `stale-while-revalidate`, `stale-if-error` doesn't allow for any asynchronous revalidation.

### Cdn Services

In a VCL service, whenever a request is made through the readthrough cache interface during a `stale-if-error` window:

- If the origin is sick (i.e. is currently failing a [health check](https://www.fastly.com/documentation/guides/concepts/healthcheck)), the stale content will be served automatically.
- If the origin is erroring (i.e. responding with a valid HTTP response such as a `503` "service unavailable"), the Fastly platform invokes `vcl_fetch` and sets `stale.exists` to `true`. The stale content may be used by [explicitly selecting it](https://www.fastly.com/documentation/guides/concepts/cache/stale#explicitly-serving-stale-content).
- If the origin is down/unreachable, the Fastly platform invokes `vcl_error` and sets `stale.exists` to `true`. The stale content may be used by [explicitly selecting it](https://www.fastly.com/documentation/guides/concepts/cache/stale#explicitly-serving-stale-content).

Once the stale period expires, the content can no longer be used as a backup and, if the origin is sick or a failure is encountered in fetching from the origin, the Fastly platform must serve an error.

If a response specifies both a `stale-while-revalidate` and a `stale-if-error` directive, the revalidation period comes first, and the error period is added on to it.

### Compute Services

In a Compute service, the readthrough cache interface does not support `stale-if-error`.

- If the origin is erroring (i.e. responding with a valid HTTP response such as a `503` "service unavailable"), the readthrough cache returns the response as served by the origin.

- If the origin is down/unreachable, the readthrough cache returns a response generated by the Fastly platform.

## Applying staleness directives only to Fastly's cache

<div id="applying-staleness-directives-to-fastly-only"></div>

`stale-*` directives apply to all caching HTTP clients, not just CDNs and other non-browser clients. While stale-serving in browsers is also useful, if you are trying to apply stale behaviors only to Fastly's cache, consider using the `Surrogate-Control` cache-control header. It functions similarly to `Cache-Control`, but overrides it if the two are both present and is removed by Fastly's cache automatically, so you can control the stale logic for it independently of browsers.

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

`Surrogate-Control` has the same spec as `Cache-Control` but Fastly's cache does not support the `s-maxage` directive (in the context of `Surrogate-Control`, `s-maxage` would mean the same thing as `max-age`, so use `Surrogate-Control: max-age`).

## Summary table

### Cdn Services

To summarize, these are the four possible freshness states that a piece of cached content can be in when it is matched by an incoming request:

- **Fresh:** Fastly's cache has a cached copy of the content, and it's within its initial freshness lifetime
- **SWR:** Fastly's cache has a stale version of the content, and it's within a `stale-while-revalidate` period
- **SIE:** Fastly's cache has a stale version, and it doesn't qualify for SWR (or that period has already expired), but it's within a `stale-if-error` period, allowing it to be used automatically if an origin is sick.
- **None:** Fastly's cache doesn't have the content or it's expired (content in this state will still allow for conditional fetches if it has an `ETag` or `Last-Modified` date)

And there are also four possible states that an origin server can be in:

- **Healthy:** The backend is up and working
- **Erroring:** The backend is returning syntactically valid HTTP responses with response status codes in the 5xx range (e.g., `503` "service unavailable")
- **Down:** The backend is unreachable, or is unable to negotiate a TCP connection
- **Sick:** Fastly's cache has marked this origin as unusable because it has consistently been unable to fetch from a health check endpoint. Origins in a _down_ or _erroring_ state that have a health check will eventually be transitioned to _sick_ by the [health check](https://www.fastly.com/documentation/guides/concepts/healthcheck).

This results in 16 possible permutations, which can be visualized as a grid to show where good things happen and where bad things happen:

The three possible outcomes are that the user will see the content they want served from the edge (😀), they'll get the content but including a blocking fetch to origin (😴), or they'll see an unfiltered error (😡), which could be either something generated by Fastly's platform or whatever your origin server returned.

> **HINT:** Some of these scenarios can be improved in VCL services by adjusting the default configuration provided by Fastly platform's to be more aggressive about using stale content. For more details, see the [serving stale](https://www.fastly.com/documentation/solutions/tutorials/full-site-delivery/serving-stale) tutorial.

### Compute Services

To summarize, these are the three possible freshness states that a piece of cached content can be in when it is matched by an incoming request:

- **Fresh:** Fastly's cache has a cached copy of the content, and it's within its initial freshness lifetime
- **SWR:** Fastly's cache has a stale version of the content, and it's within a `stale-while-revalidate` period
- **None:** Fastly's cache doesn't have the content or it's expired (content in this state will still allow for conditional fetches if it has an `ETag` or `Last-Modified` date)

> **IMPORTANT:** In a Compute service, the readthrough cache interface does not support `stale-if-error`.

And there are also three possible states that an origin server can be in:

- **Healthy:** The backend is up and working
- **Erroring:** The backend is returning syntactically valid HTTP responses with response status codes in the 5xx range (e.g., `503` "service unavailable")
- **Down:** The backend is unreachable, or is unable to negotiate a TCP connection

This results in 9 possible permutations, which can be visualized as a grid to show where good things happen and where bad things happen:

The three possible outcomes are that the user will see the content they want served from the edge (😀), they'll get the content but including a blocking fetch to origin (😴), or they'll see an unfiltered error (😡), which could be either something generated by Fastly's platform or whatever your origin server returned.

## Shielding considerations

If you have [shielding](https://www.fastly.com/documentation/guides/concepts/shielding) enabled in your service, the shield POP may serve stale content to the edge POP, which should avoid caching that content as fresh. By default, the right thing happens because the shield POP will send an `Age` header along with the response to the edge POP, and the edge POP will not cache the response because the `Age` already exceeds the object's freshness TTL (specified by a `max-age` directive).

However, in some circumstances, stale content served from a shield POP to an edge POP may be cached as if fresh:

- when it has been [purged](https://www.fastly.com/documentation/guides/concepts/cache/purging) with soft purge enabled
- where your service configuration has directly manipulated the object's TTL in edge code, such that it no longer matches the `max-age` defined on the object's response headers
- where the edge POP has made a _conditional GET_ to the shield POP and the shield POP has returned a `304` (Not Modified) response

### Cdn Services

The inadvertent caching of stale content at the edge [POP](https://www.fastly.com/documentation/guides/getting-started/concepts/using-fastlys-global-pop-network) due to these edge cases can easily be prevented in VCL services by disabling the use of stale content for asynchronous revalidation when a POP is acting as a shield:

```vcl context="sub vcl_recv { ... }"
if (fastly.ff.visits_this_service > 0) {
  set req.max_stale_while_revalidate = 0s;
}
```

This code will continue to allow stale content to be used when an origin is sick. To disable that as well, set `req.max_stale_if_error` to `0s`.

### Compute Services

Compute services do not support shielding so they do not experience this problem.

## Best practices

The following practices are recommended to get the most out of stale content:

### Cdn Services

- Specify a short `stale-while-revalidate` and a long `stale-if-error` value. If your origin is working, you don't want to subject users to content that is significantly out of date. But if your origin is down, you're probably much more willing to serve something old if the alternative is an error page.
- Always include a validator (an `ETag` or `Last-Modified` header) on responses from origin.
- Use [shielding](https://www.fastly.com/documentation/guides/concepts/shielding) to increase the cache hit ratio and increase the probability of having stale objects to serve.
- Always use [soft purges](https://www.fastly.com/documentation/guides/concepts/cache/purging#soft-vs-hard-purging) to ensure stale versions of objects aren’t also evicted.

### Compute Services

- Always include a validator (an `ETag` or `Last-Modified` header) on responses from origin.
- Always use [soft purges](https://www.fastly.com/documentation/guides/concepts/cache/purging#soft-vs-hard-purging) to ensure stale versions of objects aren’t also evicted.


