# Cache

# Changelog

import { ProductChangelog } from "~/components";

{/* <!-- Actual content lives in /src/content/changelog/cache/. Update the file there for new entries to appear here */}

<ProductChangelog product="cache" />

# Get started

import { GlossaryTooltip } from "~/components"

Cloudflare speeds up your website by caching content across globally distributed data centers.

Content can be static or dynamic. Static content — such as images, CSS, and JavaScript files — is [cacheable](/cache/concepts/default-cache-behavior/#default-cached-file-extensions) by default. Dynamic content, such as HTML pages, is not cached by default, but you can use [Cache Rules](/cache/how-to/cache-rules/) to cache it.

Cloudflare caches static content based on the following factors:

* [Caching levels](/cache/how-to/set-caching-levels/)
* [File extension](/cache/concepts/default-cache-behavior/#default-cached-file-extensions)
* Presence of [query strings](/cache/advanced-configuration/query-string-sort/)
* [Origin cache-control headers](/cache/concepts/cache-control/)
* Origin headers that indicate <GlossaryTooltip term="dynamic content">dynamic content</GlossaryTooltip>
* Cache rules that bypass cache on cookie

Cloudflare only caches resources within the Cloudflare data center that serve the request. Cloudflare does not cache off-site or third-party resources, or content hosted on [DNS-only (unproxied)](/dns/proxy-status/) DNS records.

## Learn the basics

Discover the benefits of caching with Cloudflare's CDN and understand the default cache behavior.

* [Understand what is a CDN](https://www.cloudflare.com/learning/cdn/what-is-a-cdn/)
* [Understand default cache behavior](/cache/concepts/default-cache-behavior/)
* [Understand the default file types Cloudflare caches](/cache/concepts/default-cache-behavior/#default-cached-file-extensions)

## Make more resources cacheable

Configure your settings to cache static HTML or cache anonymous page views of dynamic content.

* [Customize Caching with Cache Rules](/cache/how-to/cache-rules/)
* [Specify which resources to cache](/cache/concepts/customize-cache/)
* [Understand Origin Cache Control](/cache/concepts/cache-control/)
* [Cache by device type (Enterprise only)](/cache/how-to/cache-rules/examples/cache-device-type/)

## Improve cache HIT rates

Include or exclude query strings, optimize cache keys, or enable [Tiered Cache](/cache/how-to/tiered-cache/) to improve HIT rates and reduce traffic to your origin.

* [Choose a cache level](/cache/how-to/set-caching-levels/)
* [Enable Tiered Cache with Argo](/cache/how-to/tiered-cache/#enable-tiered-cache)
* [Configure custom cache keys (Enterprise only)](/cache/how-to/cache-keys/)
* [Enable Prefetch URLs (Enterprise only)](/speed/optimization/content/prefetch-urls/)

## Secure your cache configuration

Control resources a client is allowed to load and set access permissions to allow different origins to access your origin's resources. Protect your site from web cache deception attacks while still caching static assets.

* [Avoid web cache poisoning attacks](/cache/cache-security/avoid-web-poisoning/)
* [Configure Cross-Origin Resource Sharing (CORS)](/cache/cache-security/cors/)
* [Enable Cache Deception Armor](/cache/cache-security/cache-deception-armor/#enable-cache-deception-armor)

## Features that alter cached content

Some Cloudflare features modify your HTML or cached objects at the edge to enable optimizations or security protections.
These alterations only affect cached copies at Cloudflare's edge and do not change your original source files. Cloudflare removes the changes when you disable the feature and purge the cache.
These alterations only affect cached copies at Cloudflare's edge and do not change your original source files. The changes are removed if the feature is disabled and the cache is purged.

* [Rocket Loader](/speed/optimization/content/rocket-loader/)
* [Polish](/images/polish/)
* [Hotlink Protection](/waf/tools/scrape-shield/hotlink-protection/)
* [Email address obfuscation](/waf/tools/scrape-shield/email-address-obfuscation/)
* [Bot Management JavaScript Detections](/cloudflare-challenges/challenge-types/javascript-detections/)

## Troubleshoot

Resolve common caching concerns.

* [Learn about Cloudflare's cache response statuses](/cache/concepts/cache-responses/)
* [Investigate Cloudflare's cache response with cURL](/support/troubleshooting/general-troubleshooting/gathering-information-for-troubleshooting-sites/#troubleshoot-requests-with-curl)
* [Diagnose Always Online issues](/cache/troubleshooting/always-online/)

# Glossary

import { Glossary } from "~/components"

Review the definitions for terms used across Cloudflare's Cache documentation.

<Glossary product="cache" />

# Cloudflare Cache

import { CardGrid, Description, Feature, GlossaryTooltip, LinkTitleCard, Plan, RelatedProduct, Stream } from "~/components"

<Description>

Cache content across Cloudflare's global server network.
</Description>

<Plan type="all" />

Cache stores copies of frequently accessed content (such as images, videos, or webpages) in geographically distributed data centers that are located closer to end users than origin servers, reducing server load and improving website performance.

<Stream id="90d8a506e1e3935cff71223f9d785aa6" title="How Cloudflare protects and accelerates your website or app" thumbnail="https://imagedelivery.net/xDOJvHcv1KwTQn6S-BGFIw/f104ce23-cc3d-4f1a-fa3b-c6a894107d00/public"/>

## Features

<Feature header="Default cache behavior" href="/cache/concepts/default-cache-behavior/">

Learn about default cache behavior, default cached file extensions and cache responses.


</Feature>

<Feature header="Cache Rules" href="/cache/how-to/cache-rules/">

Configure Cache Rules to optimize your website by specifying which resources should be cached and for how long.


</Feature>

<Feature header="Tiered Cache" href="/cache/how-to/tiered-cache/">

Enable Tiered Cache to optimize content delivery by caching frequently accessed content in multiple locations for faster delivery and reduced origin traffic.


</Feature>

<Feature header="Cache Reserve" href="/cache/advanced-configuration/cache-reserve/">

Use Cloudflare's persistent storage to increase cache times.


</Feature>

<Feature header="Purge" href="/cache/how-to/purge-cache/">

Instantly purge cached files to force Cloudflare to fetch fresh versions from your web server files. You can purge specific files or all at once.


</Feature>

***

## Related products

<RelatedProduct header="Load Balancing" href="/load-balancing/" product="load-balancing">
Cloudflare Load Balancing distributes traffic across your <GlossaryTooltip term="endpoint">endpoints</GlossaryTooltip>, reducing endpoint strain and latency and improving the end users experience.
</RelatedProduct>

<RelatedProduct header="Images" href="/images/" product="images">
A suite of products tailored to your image-processing needs.
</RelatedProduct>

<RelatedProduct header="Workers" href="/workers/" product="workers">
Cloudflare Workers allows developers to build serverless applications and deploy instantly across the globe for exceptional performance, reliability, and scale.
</RelatedProduct>

<RelatedProduct header="Rules" href="/rules/" product="rules">
Cloudflare Rules allows you to make adjustments to requests and responses, configure Cloudflare settings, and trigger specific actions for matching requests.
</RelatedProduct>

<RelatedProduct header="Cloudflare Network Interconnect" href="/network-interconnect/" product="network-interconnect">
Cloudflare Network Interconnect (CNI) allows you to connect your network infrastructure directly with Cloudflare – rather than using the public Internet – for a more reliable and secure experience.
</RelatedProduct>

<RelatedProduct header="R2" href="/r2/" product="r2">
Cloudflare R2 Storage allows developers to store large amounts of unstructured data without the costly egress bandwidth fees associated with typical cloud storage services.
</RelatedProduct>

<RelatedProduct header="Dedicated CDN Egress IPs" href="/smart-shield/configuration/dedicated-egress-ips/" product="smart-shield">
Smart Shield Advanced provides dedicated egress IPs (from Cloudflare to your origin) for your layer 7 WAF and CDN services, as well as Spectrum.
</RelatedProduct>

***

## More resources

<CardGrid>

<LinkTitleCard title="Plans" href="https://www.cloudflare.com/cdn/" icon="document">
Compare available Cloudflare plans
</LinkTitleCard>

<LinkTitleCard title="Pricing" href="https://www.cloudflare.com/plans/#overview" icon="seti:shell">
Explore pricing options for Cache
</LinkTitleCard>

</CardGrid>

# Cache Reserve

import { Render, TabItem, Tabs, APIRequest, DashButton } from "~/components";

<Render file="smart-shield-callout" product="smart-shield" />

<Render file="cache-reserve-intro" product="cache" />

![Content served from origin and getting cached in Cache Reserve, and Edge Cache Data Centers (T1=upper-tier, T2=lower-tier) on its way back to the client](~/assets/images/cache/content-being-served.png)

Content in Cache Reserve is considered fresh based on the [Edge Cache TTL](/cache/how-to/edge-browser-cache-ttl/#edge-cache-ttl) setting, or your origin's `Cache-Control` headers if Edge Cache TTL is not set. After the freshness period expires, Cloudflare revalidates the asset with your origin the next time it is requested. This is the same behavior as in Cloudflare's regular CDN.

The retention period controls how long an asset stays in Cache Reserve before it is removed. Cache Reserve starts with a retention period of 30 days. If an asset is not requested within the retention period, it is removed from Cache Reserve. Requesting the asset resets the retention period.

Assets must [meet certain criteria](#cache-reserve-asset-eligibility) to use Cache Reserve.

Cache Reserve is a usage-based product and [pricing](#pricing) is detailed below. While Cache Reserve does require a paid plan, users can continue to use Cloudflare’s CDN (without Cache Reserve) for free.

## Enable Cache Reserve

A paid Cache Reserve plan is required.

<Tabs syncKey="dashPlusAPI"> <TabItem label="Dashboard">

1. In the Cloudflare dashboard, go to the **Cache Reserve** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-reserve" />

2. Select **Enable storage sync**.

</TabItem> <TabItem label="API">

Refer to the [Change Cache Reserve setting API](/api/resources/cache/subresources/cache_reserve/methods/edit/) for more information.

</TabItem> </Tabs>

:::note[Note]
You can pause Cache Reserve at any time. Pausing Cache Reserve means that Cloudflare’s network will no longer use Cache Reserve to serve data, but resources will remain in storage until they are purged or expired.
:::

If you are an Enterprise customer and are interested in Cache Reserve, contact your account team to get help with your configuration.

## Cache Reserve asset eligibility

<Render file="cache-reserve-eligibility" product="cache" />

## Purge behavior

[Purge](/cache/how-to/purge-cache/) forces a cache miss for matching Cache Reserve content, regardless of purge type. On the next request, your origin must deliver the content in full, and Cloudflare writes it to Cache Reserve again.

Purging by URL deletes the content from Cache Reserve. Purging by tag, hostname, prefix, or everything does not delete it right away. Matching content continues to incur storage costs until a later request replaces it or its retention period ends.

[Invalidation](/cache/guides/invalidate-cache/) keeps matching content in Cache Reserve and marks it as stale. If your origin responds with `304 Not Modified`, Cloudflare reuses the stored content instead of fetching it again. Updating the stored content after the `304` response is a Class A operation.

To refresh metadata set by [Cache Response Rules](/cache/how-to/cache-response-rules/), such as cache tags, purge the affected content. A `304 Not Modified` response to an invalidated request does not refresh this metadata.

To remove all data from Cache Reserve, refer to [Cache Reserve clear button](#cache-reserve-clear-button).

## Limits

<Render file="cache-reserve-limits" product="cache" />

## Usage

Like the standard CDN, Cache Reserve also uses the `cf-cache-status` header to indicate [cache response statuses](/cache/concepts/cache-responses/) like `MISS`, `HIT`, and `REVALIDATED`. Cache Reserve cache misses and hits are factored into the dashboard's cache hit ratio.

Individual sampled requests that filled or were served by Cache Reserve are viewable via the [CacheReserveUsed](/logs/logpush/logpush-job/datasets/zone/http_requests/) Logpush field.

Cache Reserve monthly operations and storage usage are viewable in the dashboard.

## Pricing

Cache Reserve charges based on the total volume of data stored, along with two classes of operations on that data:

- [Class A operations](/r2/pricing/#class-a-operations) which are more expensive and tend to mutate state.
- [Class B operations](/r2/pricing/#class-b-operations) which tend to read existing state.

In most cases, a Cache Reserve miss will result in both one class A and one class B operation, and a Cache Reserve hit will result in one class B operation. Assets larger than 1 GB will incur more operations proportional to their size.

### Cache Reserve pricing

<table>
	<tbody>
		<th></th>
		<th>Rates</th>
		<tr>
			<td>Storage</td>
			<td>$0.015 / GB-month</td>
		</tr>
		<tr>
			<td>Class A Operations (writes)</td>
			<td>$4.50 / million requests</td>
		</tr>
		<tr>
			<td>Class B Operations (reads)</td>
			<td>$0.36 / million requests</td>
		</tr>
	</tbody>
</table>

<Render file="cache-reserve-billing-note" product="cache" />

### Storage usage

Storage is billed using gigabyte-month (GB-month) as the billing metric. A GB-month is calculated by recording total bytes stored for the duration of the month.

For example:

- Storing 1 GB for 30 days will be charged as 1 GB-month.
- Storing 2 GB for 15 days will be charged as 1 GB-month.

### Operations

<Render
  file="cache-reserve-operations"
  product="cache"
/>

## Cache Reserve billing examples

#### Example 1

Assuming 1,000 assets (each 1 GB) are written to Cache Reserve at the start of the month and each asset is read 1,000 times, the estimated cost for the month would be:

|                    | Usage                                     | Billable Quantity | Price      |
| ------------------ | ----------------------------------------- | ----------------- | ---------- |
| Class B Operations | (1,000 assets) \* (1,000 reads per asset) | 1,000,000         | $0.36      |
| Class A Operations | (1,000 assets) \* (1 write per asset)     | 1,000             | $4.50      |
| Storage            | (1,000 assets) \* (1GB per asset)         | 1,000 GB-months   | $15.00     |
| **TOTAL**          |                                           |                   | **$19.86** |
|                    |                                           |                   |            |

<Render file="cache-reserve-billing-note" product="cache" />

#### Example 2

Assuming 1,000,000 assets (each 1 MB) are in Cache Reserve, and:

- each asset expires and is rewritten into Cache Reserve 1 time per day
- each asset is read 2 times per day

the estimated cost for the month would be:

|                    | Usage                                                | Billable Quantity | Price       |
| ------------------ | ---------------------------------------------------- | ----------------- | ----------- |
| Class B Operations | (1,000,000 assets) \* (2 reads per day) \* (30 days) | 60,000,000        | $21.60      |
| Class A Operations | (1,000,000 assets) \* (1 write per day) \* (30 days) | 30,000,000        | $135.00     |
| Storage            | (1,000,000 assets) \* (1MB per asset)                | 1,000 GB-months   | $15.00      |
| **TOTAL**          |                                                      |                   | **$171.60** |
|                    |                                                      |                   |             |

<Render file="cache-reserve-billing-note" product="cache" />

## Tips and best practices

Cache Reserve is designed for use with [Tiered Cache](/cache/how-to/tiered-cache/) enabled for maximum origin shielding. Using Cache Reserve without Tiered Cache may result in higher storage operation costs. The Cloudflare dashboard will warn you if you try to enable Cache Reserve without Tiered Cache.

## Cache Reserve Analytics

Cache Reserve Analytics provides insights regarding your Cache Reserve usage. It allows you to check what content is stored in Cache Reserve, how often it is being accessed, how long it has been there and how much egress from your origin it is saving you.

In the **Overview** section, under **Cache Reserve**, you have access to the following metrics:

- **Egress savings (bandwidth)** - is an estimation based on response bytes served from Cache Reserve that did not need to be served from your origin server. These are represented as cache hits.
- **Requests served by Cache Reserve** - is the number of requests served by Cache Reserve (total).
- **Data storage summary** - is based on a representative sample of requests. Refer to [Sampling](/analytics/graphql-api/sampling/) for more details about how Cloudflare samples data.
  - **Current data stored** - is the data stored (currently) over time.
  - **Aggregate storage usage** - is the total of storage used for the selected timestamp.
- **Operations** - Class A (writes) and Class B (reads) operations over time.

## Cache Reserve clear button

You can remove all data stored in Cache Reserve through the dashboard or via API. To clear your cache reserve:

- Cache Reserve must have already been enabled for the zone.
- Cache Reserve needs to be off.

Be aware that the deletion may take up to 24 hours to complete.

<Tabs syncKey="dashPlusAPI"> <TabItem label="Dashboard">


1. In the Cloudflare dashboard, go to the **Cache Reserve** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-reserve" />

2. In **Delete Cache Reserve Data**, select **Delete Storage**.

</TabItem> <TabItem label="API">

To delete Cache Reserve data via API use the following example requests. For more information, refer to the [API documentation](/api/resources/cache/subresources/cache_reserve/methods/clear/).

**Request 1: Get Cache Reserve status**

<APIRequest path="/zones/{zone_id}/cache/cache_reserve" method="GET" />

```json title="Response"
{
	"result": {
		"editable": true,
		"id": "cache_reserve",
		"value": "off"
	},
	"success": true,
	"errors": [],
	"messages": []
}
```

If Cache Reserve is turned off, you can proceed to the Cache Reserve Clear operation.

**Request 2: Start Cache Reserve Clear**

<APIRequest path="/zones/{zone_id}/cache/cache_reserve_clear" method="POST" />

```json title="Response"
{
	"result": {
		"id": "cache_reserve_clear",
		"start_ts": "2024-06-02T10:00:00.12345Z",
		"state": "In-progress"
	},
	"success": true,
	"errors": [],
	"messages": []
}
```

</TabItem> </Tabs>

# Crawler Hints

import { DashButton } from "~/components"

Crawler Hints uses Cloudflare cache signals to tell search engines when your content has likely changed, so they crawl your site at the right time instead of guessing.

## Background

Search engines and similar services operate massive networks of bots that crawl the Internet to identify the content most relevant to a user query. Content on the web is always changing though, and search engine crawlers must continually wander the Internet and guess how frequently they should check a site for content updates.

With Crawler Hints, Cloudflare can proactively tell a crawler about the best time to index or when content changes. Additionally, Crawler Hints supports [IndexNow](https://www.indexnow.org/), which allows websites to notify search engines whenever content on their website content is created, updated, or deleted. Crawler Hints uses cache-status [`MISS`](/cache/concepts/cache-responses/#miss) to determine when content has likely been updated and sends it to IndexNow's crawler. If an asset's response has an HTTP status code greater than 4xx, the Crawler hints will not report that to [IndexNow](https://www.indexnow.org/).

## Benefits

Crawler Hints help search engines and other bot-powered services serve the freshest version of your content, which can improve search rankings.

Crawler Hints also reduces unnecessary crawl traffic to your origin, lowering resource consumption and improving site performance.

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |

## Enable Crawler Hints

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Enable **Crawler Hints**.

After enabling Crawler Hints, Cloudflare will begin sending hints to search engines about when they should crawl particular parts of your website.

## Prevent indexing for a specific page

When enabled, Crawler Hints is a global setting for your entire website. You can stop a specific page from being indexed by either:

* Having the origin server send through the header `X-Robots-Tag: noindex` on any pages that should not be indexed.
* Including `<meta name="robots" content="noindex, nofollow" />` in the HTML of any pages that should not be indexed.
* Creating a [Response header Transform Rule](/rules/transform/response-header-modification/) in Cloudflare to add the `X-Robots-Tag: noindex` header instead of doing it from the origin server.

# Early Hints

import { DashButton } from "~/components"

When a browser requests a page, the origin server takes time to prepare the full response. Early Hints uses this wait time to send the browser a preliminary `103` response containing `Link` headers that tell the browser which assets it will need. The browser can start loading those assets before the full response arrives, which speeds up page loads.

Early Hints is defined in [RFC 8297](https://httpwg.org/specs/rfc8297.html) as a new HTTP status code (`103 Early Hints`). Cloudflare caches and serves these `103` responses with `Link` headers from your HTML pages, reducing user-perceived latency.

:::note[Note]


Early Hints is currently only supported over HTTP/2 and HTTP/3.


:::

For more information about Early Hints, refer to the [Cloudflare](https://blog.cloudflare.com/early-hints) and [Google Chrome](https://developer.chrome.com/en/blog/early-hints/) blogs.

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |

## Enable Early Hints

1. In the Cloudflare dashboard, go to the **Speed** > **Settings** page.

   <DashButton url="/?to=/:account/:zone/speed/optimization" />

2. Go to the **Content Optimization** tab.
3. For **Early Hints**, toggle the switch to **On**.

## Generate Early Hints

Early Hints are only generated and cached:

* For URIs with `.html`, `.htm`, or `.php` file extensions, or no file extension
* On 200, 301, or 302 response return codes
* When the response contains [link headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link) with preconnect or preload rel types, such as `Link: </img/preloaded.png>; rel=preload`

:::note


Early Hints cache entries are keyed by request URI and ignore query strings.


:::

## Emit Early Hints

Cloudflare will asynchronously look up and emit a cached 103 Early Hints response ahead of a main response.

Currently, only certain browser versions will take action to preload or preconnect on receiving Early Hints, such as Google Chrome M94 and higher. Instructions for running WebPageTest to experiment with compatible client browsers can be found in the [blog post](https://blog.cloudflare.com/early-hints/#testing-early-hints-with-web-page-test).

Additionally, keep the following in mind:

* Early Hints responses may be emitted before reaching the origin server or Worker. When Early Hints is enabled and pages on your site require authentication, unauthenticated visitors may receive a 103 response. The 103 response would contain cached Link headers and be sent before a 403 Forbidden response from your origin.
* Early Hints may be emitted less frequently on requests where the content is cacheable. Cloudflare CDN is more likely to retrieve a response header before the asynchronous Early Hints lookup finishes if the response has been cached. Cloudflare will not send a 103 response if the main response header is already available.
* Cloudflare currently disables Early Hints on some User-Agents, for example, select search crawler bots that show incompatibility with 1xx responses.
* You may see an influx of `504` responses with `ClientRequestSource: earlyHintsCache` in Cloudflare Logs when Early Hints is enabled, which is expected and benign. Requests with this source are internal subrequests for cached Early Hints; they are neither end user requests, nor do they reach your origin. Their response status only indicates whether there are cached Early Hints for the request URI (`200` on cache HIT, `504` on cache MISS). These requests are already filtered out in other views, such as Cache Analytics. To filter them out of Log Explorer, use `ClientRequestSource != "earlyHintsCache"`. To filter using the GraphQL API, refer to [Filter end users](/analytics/graphql-api/features/filtering/#filter-end-users).

# Advanced configuration

import { DirectoryListing } from "~/components"

Learn how to complete different configuration options to customize your cache behavior.

<DirectoryListing />

# Query String Sort

**Query String Sort** increases cache-hit rates by first sorting query strings into a consistent order before checking the Cloudflare cache.

By default, Cloudflare’s cache treats resources as distinct if their URL query strings are in a different order. For instance, these resources are cached separately:

* `/video/48088296?title=0&byline=0&portrait=0&color=51a516`
* `/video/48088296?byline=0&color=51a516&portrait=0&title=0`

Query String Sort changes this behavior. If two query strings exist with the same name, the URL is sorted by the parameter value. For example:

`/example/file?word=alpha&word=beta` and `/example/file?word=beta&word=alpha`

would be sorted to:

`/example/file?word=alpha&word=beta`

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | No | No | No | Yes |

***

## Enable Query String Sort

To enable Query String Sort:

1. Log into the [Cloudflare dashboard](https://dash.cloudflare.com).
2. Select your account and zone.
3. Go to **Caching** > **Configuration**.
4. For **Enable Query String Sort**, switch the toggle to **On**.

***

## Unexpected behavior with WordPress admin pages

When a site or an application requires exact query string ordering, enabling Query String Sort might cause unexpected behavior.

For example in the WordPress admin UI, you might notice any of the following behaviors:

* No media appear in the Media Library
* Inability to customize the site via **Appearance** > **Customize**
* Inability to drag any widget to a sidebar in **Appearance** > **Widgets**
* Inability to edit menus in **Appearance** > **Menus**

To understand why this happens, note that WordPress [concatenates JavaScript files](https://developer.wordpress.org/advanced-administration/wordpress/wp-config/#disable-javascript-concatenation) to speed up the administration interface. The way WordPress implements this involves multiple occurrences of `load[]` parameters in the query string, where the order of those parameters is crucial.

:::note


Note that more recent versions of WordPress may not experience this issue, as a patch has been implemented in WordPress since 2019. The patch can be found at [WordPress Core Trac Changeset 45456](https://core.trac.wordpress.org/changeset/45456).


:::

### Identify the problem

The screenshot below shows an example where resources in the Media Library are not rendered correctly and the browser debugging console reveals that the page is throwing an error:

![Resources in the Media Library are not rendered correctly](~/assets/images/support/media_library_enabling_query.png)

When the page `load-scripts.php` loads, the browser sends a request to Cloudflare for:

```txt
/wp-admin/load-scripts.php?c=0&load%5B%5D=hoverIntent,common,admin-bar,underscore,shortcode,backbone,wp-util,wp-backbone,media-models,wp-plupload,wp-mediaelement,wp-api-r&load%5B%5D=equest,media-views,media-editor,media-audiovideo,mce-view,imgareaselect,image-edit,media-grid,media,svg-painter&ver=5.0.3
```

With Query String Sort enabled, Cloudflare will then sort the parameters and values in the request query string, resulting in the following:

```txt
/wp-admin/load-scripts.php?c=0&load%5B%5D=equest,media-views,media-editor,media-audiovideo,mce-view,imgareaselect,image-edit,media-grid,media,svg-painter&load%5B%5D=hoverIntent,common,admin-bar,underscore,shortcode,backbone,wp-util,wp-backbone,media-models,wp-plupload,wp-mediaelement,wp-api-r&ver=5.0.3
```

Note that the `load[]` parameters were swapped, as `equest` should come before `hoverIntent` when alphabetically ordered.

When this happens, you will most likely find errors in the browser console, such as:

`_____ is not defined at load-scripts.php?c=0&load[]=...`

This type of error indicates that Query String Sort is inadvertently breaking some WordPress admin page functionality.

After sorting, the query then goes to Cloudflare's cache infrastructure (and to the origin server, if the resource is not in the Cloudflare cache or is not cacheable). The origin server then serves the concatenated scripts, which are ordered differently. Because scripts might depend on other scripts, this process might break dependencies.

### Respond to the issue

Start by analyzing your site or application behavior around the use of query strings. Do you have assets served with multiple possible arrangements of query strings?

For example, you might have an image resizing endpoint or a search form, where the order of query parameters might vary - such as width, height, and version - yet a unique parameter combination points to a single relevant asset.

To minimize problems, consider:

* Disabling **Query String Sort** for the site if you’re sure that this feature does not add value to any part of your site. Cloudflare disables this option by default in the **Caching** app.
* Use Cache Rules to enable **Query String Sort** (set **Cache key** > **Sort query string**: `On`) for URLs where preserving the query string parameter order is not important.
* Alternatively, use Cache Rules to disable **Query String Sort** for URLs where a specific parameter order is required. For example, set **Cache key** > **Sort query string**: `Off` for URI paths starting with `/wp-admin/load-scripts.php`, or for any URLs with similar requirements.

To learn more about Cache Rules, visit [Cache Rules](/cache/how-to/cache-rules/).

***

## Related resources

* [Increasing Cache Hit Rates with Query String Sort](https://blog.cloudflare.com/increasing-cache-hit-rates-with-query-string-sort/)
* [Best Practice: Caching Everything While Ignoring Query Strings](/cache/how-to/cache-rules/examples/cache-everything-ignore-query-strings/)

# Serving tailored content with Cloudflare

import { DashButton, APIRequest } from "~/components";

Content negotiation is the practice of serving different versions of a resource from a single URL, tailoring the experience to the end user. Common examples include delivering content in a specific language (`Accept-Language`), optimizing for a device (`User-Agent`), or serving modern image formats (`Accept`).

Cloudflare's global network is designed to handle this at scale. For common scenarios such as serving next-generation images, this negotiation is streamlined with a dedicated feature. For more customized logic, Cloudflare provides a toolkit including Transform Rules, Snippets, Custom Cache Keys, and Workers, giving you granular control to ensure the right content is served to every user, every time.

---

## Use query strings

The [Transform Rule](/rules/transform/) method is ideal when you can create a distinct URL, such as serving content based on a visitor's location.

### Geolocation example

In this example, you run an e-commerce site and want to display prices in the local currency based on the visitor's country.

1. In the Cloudflare dashboard, go to the Rules **Overview** page.

   <DashButton url="/?to=/:account/:zone/rules/overview" />

2. Select **Create rule** and select the option **URL Rewrite Rule**.
3. Enter a descriptive name, such as `Vary by Country - Canada`.
4. In **If incoming requests match...**, select **Custom filter expression**.
5. Under **When incoming requests match...**, create the following expression:
   - **Field:** `Country`
   - **Operator:** `equals`
   - **Value:** `Canada`
6. Under **Then...**
    - for **Path**, select **Preserve**.
    - for **Query**, select **Rewrite to**: **Dynamic** `loc=ca`
7. Select **Save**.

Now, requests from Canada to `/products/item` will be transformed to `/products/item?loc=ca` before reaching your origin or the cache, creating a distinct cache entry.

:::note[Availability]
Free, Pro, Business, and Enterprise plans
:::

---

## Vary for Images

[Vary for Images](/cache/advanced-configuration/vary-for-images/) tells Cloudflare which variants your origin supports. Cloudflare then caches each version separately and serves the correct one to browsers without contacting your origin each time. This feature is managed via the Cloudflare API.

### Enable Vary for Images

To enable this feature, create a _variants rule_ using the API. This rule maps file extensions to the image formats your origin can serve.

For example, the following API call tells Cloudflare that for `.jpeg` and `.jpg` files, your origin can serve `image/webp` and `image/avif` variants:

<APIRequest
  path="/zones/{zone_id}/cache/variants"
  method="PATCH"
  json={{
    value: {
      jpeg: ["image/webp", "image/avif"],
      jpg: ["image/webp", "image/avif"]
    }
  }}
/>

After creating the rule, Cloudflare will create distinct cache entries for each image variant, improving performance for users with modern browsers.

:::note[Availability]
Pro, Business, and Enterprise plans
:::

## Use Snippets for programmatic caching

[Snippets](/rules/snippets/) are self-contained JavaScript fetch handlers that run at the edge on your requests through Cloudflare. They allow you to programmatically interact with the cache, providing full control over the cache key and response behavior without changing the user-facing URL.

### Example: A/B testing

In this example, you run an A/B test controlled by a cookie named `ab-test` (with values `group-a` or `group-b`). You want to cache a different version of the page for each group.

1. In the Cloudflare dashboard, go to the **Snippets** page.

   <DashButton url="/?to=/:account/:zone/rules/snippets" />

2. Select **Create new Snippet** and name it `ab-test-caching`.
3. Paste the following code. It modifies the cache key based on the `ab-test` cookie and caches the response for 30 days.

```js
const CACHE_DURATION = 30 * 24 * 60 * 60; // 30 days

export default {
  async fetch(request) {
    // Construct a new URL for the cache key based on the A/B cookie
    const abCookie = request.headers.get('Cookie')?.match(/ab-test=([^;]+)/)?.[1] || 'control';
    const url = new URL(request.url);
    url.pathname = `/ab-test/${abCookie}${url.pathname}`;

    const cacheKey = new Request(url, request);
    const cache = caches.default;

    let response = await cache.match(cacheKey);
    if (!response) {
      // If not in cache, fetch from origin
      response = await fetch(request);
      response = new Response(response.body, response);
      response.headers.set("Cache-Control", `s-maxage=${CACHE_DURATION}`);
      // Put the response into cache with the custom key
      await cache.put(cacheKey, response.clone());
    }
    return response;
  },
};
```

4. Save and deploy the Snippet.
5. From the Snippets dashboard, select **Attach to routes** to assign the Snippet.

:::note[Availability]
Pro, Business, and Enterprise plans
:::

## Custom Cache Keys (Enterprise)

If your account is on an Enterprise plan, the [Custom Cache Keys](/cache/how-to/cache-keys) feature provides a no-code interface to define which request properties are included in the cache key.

Custom Cache Key options:

- Cache by device type
- Query string option `No query string parameters except`
- Include headers and values
- Include cookie names and values
- User: Device type, Country, Language

### Example: Same URL, different content

If your origin serves different content types (for example, `application/json` vs. `text/html`) at the same URL based on the `Accept` header, use a custom cache key to cache them separately.

1. In the Cloudflare dashboard, go to the **Cache Rules** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

2. Select **Create rule**.
3. Enter  rule name, such as `Vary by Accept Header`.
4. Set the condition for the rule to apply (for example, a specific hostname or path).
5. Under **Cache key**, select **Use custom key**.
6. Select **Add new**.
    - **Type**: `Header`
    - **Name**: `Accept`
    - **Value**: Add each `value`, or leave empty for all.
7. Select **Deploy**.

This configuration creates separate cache entries based on the `Accept` header value, respecting your API's content negotiation.

:::note[Availability]
Enterprise plans only
:::

## Use Cloudflare Workers for advanced logic

For complex caching scenarios, [Cloudflare Workers](/cache/interaction-cloudflare-products/workers/) provide a full serverless environment ideal for custom logic at scale.

### Example: Device type – Free/Pro/Biz (without Tiered Cache)

This Worker detects whether a visitor is on a mobile or desktop device and creates separate cache entries for each, ensuring the correct version of the site is served and cached.

```js
export default {
  async fetch(request, env, ctx) {
    const userAgent = request.headers.get('User-Agent') || '';
    const deviceType = userAgent.includes('Mobile') ? 'mobile' : 'desktop';

    // Create a new URL for the cache key that includes the device type
    const url = new URL(request.url);
    url.pathname = `/${deviceType}${url.pathname}`;

    const cacheKey = new Request(url, request);
    const cache = caches.default;

    let response = await cache.match(cacheKey);

    if (!response) {
      console.log(`Cache miss for ${deviceType} device. Fetching from origin.`);
      response = await fetch(request);
      let responseToCache = response.clone();
      ctx.waitUntil(cache.put(cacheKey, responseToCache));
    }

    return response;
  },
};
```

:::note[Availability]
Free and Paid plans
:::

### Example: Device type – Enterprise (with Tiered Cache)

This Worker detects if a visitor is on a mobile device or a desktop and creates a separate cache entry for each, ensuring the correct version of the site is served and cached. Uses the Enterprise `cf.customCacheKey` feature.

```js
export default {
  async fetch(request) {
    // 1. Determine the device type from the User-Agent header
    const userAgent = request.headers.get('User-Agent') || '';
    const deviceType = userAgent.includes('Mobile') ? 'mobile' : 'desktop';

    // 2. Create a custom cache key by appending the device type to the URL
    const customCacheKey = `${request.url}-${deviceType}`;

    // 3. Fetch the response. Cloudflare's cache automatically uses the
    //    customCacheKey for cache operations (match, put).
    const response = await fetch(request, {
      cf: {
        cacheKey: customCacheKey,
      },
    });

    // Optionally, you can modify the response before returning it
    // For example, add a header to indicate which cache key was used
    const newResponse = new Response(response.body, response);
    newResponse.headers.set("X-Cache-Key", customCacheKey);
    return newResponse;
  },
};
```

:::note[Availability]
Enterprise only
:::

## Example: Caching Next.js RSC payloads

A common challenge is caching content from frameworks like Next.js, which uses an `RSC` (React Server Components) request header to differentiate between HTML page loads and RSC data payloads for the same URL. Here are the best ways to handle this.

### Method 1: Transform Rules

The simplest solution is to create a [Transform Rule](/rules/transform/) that checks for the `RSC` header and adds a unique query parameter on the request, creating two distinct cacheable URLs: `/page` (for HTML) and `/page?_rsc=1` (for the RSC payload).

1. In the Cloudflare dashboard, go to the Rules **Overview** page.

   <DashButton url="/?to=/:account/:zone/rules/overview" />

2. Select **Create rule** and select the option **URL Rewrite Rule**.
3. Enter a name, such as `Vary by RSC Header`.
4. In **If incoming requests match**, select **Custom filter expression**.
5. Under **When incoming requests match**, manually edit the expression so that it checks for the presence of the `RSC` header:
    - `has_key(http.request.headers, "rsc")`

6. Under **Then**:
    - For **Path**, select **Preserve**.
    - For **Query**, select **Rewrite to**, select **Static**: `_rsc=1`.

7. Select **Save**.

### Method 2: Snippets or Custom Cache Keys

Alternatively, use [Snippets](/rules/snippets/) or [Custom Cache Keys](/cache/how-to/cache-keys) to add the `RSC` header directly to the cache key without modifying the visible URL. This provides a cleaner URL but requires more advanced configuration.

:::note[Availability]
- Snippets: Pro, Business, Enterprise
- Custom Cache Keys: Enterprise only
:::

# Vary for images

import { Details, APIRequest } from "~/components"

The `Vary` HTTP response header tells Cloudflare that an origin can serve different versions of the same resource depending on the request headers. For images, this allows your origin to serve modern formats like WebP or AVIF to browsers that support them, while continuing to serve JPEG or PNG to others.

When Cloudflare receives a response with image variants, it caches each variant separately. Subsequent requests from browsers with the same image format preferences are served directly from cache without contacting your origin.

Vary for Images works by parsing the `Accept` header in each request to determine which image format the browser supports, then serving the matching cached variant.

Vary for images is available for Pro, Business, and Enterprise customers.

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | No | Yes | Yes | Yes |

## File extensions

You can use vary for images on the file extensions below if the origin server sends the `Vary: Accept` response header. If the origin server sends `Vary: Accept` but does not serve the set variant, the response is not cached and displays `BYPASS` in the cache status in the response header. Additionally, the list of variant types the origin serves for each extension must be configured so that Cloudflare decides which variant to serve without contacting the origin server.


<Details header="File extensions enabled for varying">
  <ul>
    <li>.avif</li>
    <li>.bmp</li>
    <li>.gif</li>
    <li>.jpg</li>
    <li>.jpeg</li>
    <li>.jp2</li>
    <li>.png</li>
    <li>.tif</li>
    <li>.tiff</li>
    <li>.webp</li>
  </ul>
</Details>

## Enable vary for images

Vary for Images is enabled through Cloudflare's API by creating a variants rule. In the examples below, learn how to serve JPEG, WebP, and AVIF variants for `.jpeg` and `.jpg` extensions.

### Create a variants rule

<APIRequest
  path="/zones/{zone_id}/cache/variants"
  method="PATCH"
  json={{
    value: {
      jpeg: ["image/webp", "image/avif"],
      jpg: ["image/webp", "image/avif"]
    }
  }}
/>

### Modify to only allow WebP variants

<APIRequest
  path="/zones/{zone_id}/cache/variants"
  method="PATCH"
  json={{
    value: {
      jpeg: ["image/webp"],
      jpg: ["image/webp"]
    }
  }}
/>

### Delete the rule

<APIRequest
  path="/zones/{zone_id}/cache/variants"
  method="DELETE"
/>

### Get the rule

<APIRequest
  path="/zones/{zone_id}/cache/variants"
  method="GET"
/>

To learn more about purging varied images, refer to [Purge varied images](/cache/how-to/purge-cache/purge-varied-images/).

## Limitations

* For Vary for images to work, your image URLs must include the file extension in the path and not the query string. For example the URL `https://example.com/image.jpg` is compatible but `https://example.com/index.php?file=image.jpg` is not compatible.
* Your origin must return an image type matching the file extension in the URL when a HTTP client sends no `Accept` header, or an `Accept: */*` header. Otherwise, you will see `CF-Cache-Status: BYPASS` in the HTTP response headers.

# Avoid web cache poisoning

A cache poisoning attack uses an HTTP request to trick an origin web server into responding with a harmful resource that has the same cache key as a clean request. As a result, the poisoned resource gets cached and served to other users.

A Content Delivery Network (CDN) like Cloudflare relies on cache keys to compare new requests against cached resources. The CDN then determines whether the resource should be served from the cache or requested directly from the origin web server.

## Learn about Cache Poisoning

To deepen your understanding of the risks and vulnerabilities associated with cache poisoning, consult the following resources:

* [Practical Web Cache Poisoning](https://portswigger.net/blog/practical-web-cache-poisoning)
* [How Cloudflare protects customers from cache poisoning](https://blog.cloudflare.com/cache-poisoning-protection/)

## Only cache files that are truly static

Review the caching configuration for your origin web server and ensure you are caching files that are static and do not depend on user input in any way. To learn more about Cloudflare caching, review:

* [Which file extensions does Cloudflare cache for static content?](/cache/concepts/default-cache-behavior/)
* [How Do I Tell Cloudflare What to Cache?](/cache/how-to/cache-rules/)

## Do not trust data in HTTP headers

Attackers can exploit HTTP headers to inject malicious content into cached responses. For example, if your application reflects an untrusted header value in the response body, an attacker could use this to perform cross-site scripting (XSS) through the cache. To reduce this risk:

* Do not rely on values in HTTP headers if they are not part of your [cache key](/cache/how-to/cache-keys/).
* Do not include untrusted header values in your response body.

## Do not trust GET request bodies

Cloudflare caches contents of GET request bodies, but they are not included in the cache key. GET request bodies should be considered untrusted and should not modify the contents of a response. If a GET body can change the contents of a response, consider bypassing cache or using a POST request.

## Monitor web security advisories

To keep informed about Internet security threats, Cloudflare recommends that you monitor web security advisories on a regular basis. Some of the more popular advisories include:

* [Drupal Security Advisories](https://www.drupal.org/security)
* [Symfony Security Advisories](https://symfony.com/blog/category/security-advisories)
* [Laminas Security Advisories](https://getlaminas.org/security/advisories)

# Cache Deception Armor

import { DashButton } from "~/components";

## Web Cache Deception attacks

A Web Cache Deception attack tricks a user into visiting a URL that appears to point to a static asset but actually returns dynamic, personalized content from the origin.

This attack works when an origin treats requests to non-existent paths as equivalent to a parent path — for example, when `http://www.example.com/newsfeed` is a dynamic page that returns different content for each authenticated user, and the origin also serves that same response for `/newsfeed/foo.jpg`. Because the path ends in `.jpg`, Cloudflare caches the response by default. The attacker then visits the same URL and receives the cached copy of the user's personalized content.

## Cache Deception Armor protects against attacks

You can protect users from Web Cache Deception attacks by [creating a cache rule](/cache/cache-security/cache-deception-armor/#enable-cache-deception-armor). With this rule, you can continue to cache static assets, but the rule will verify a URL's extension matches the returned `Content-Type`.

In the newsfeed example above, if `http://www.example.com/newsfeed` is a script that outputs a webpage, the `Content-Type` is `text/html`. On the other hand, `http://www.example.com/newsfeed/foo.jpg` is expected to have `image/jpeg` as `Content-Type`. When a mismatch that could result in a Web Cache Deception attack is found, Cloudflare does not cache the response.

### Exceptions

* If the returned `Content-Type` is `application/octet-stream`, the extension does not matter because that is typically a signal to instruct the browser to save the asset instead of to display it.
* Cloudflare allows `.jpg` to be served as `image/webp` or `.gif` as `video/webm` and other cases that are unlikely to be attacks.
* Keep in mind that Cache Deception Armor depends upon [Origin Cache Control](/cache/concepts/cache-control/). A `Cache-Control` header from the origin, or an [Edge Cache TTL Cache Rule](/cache/how-to/cache-rules/settings/#edge-ttl) may override the protection.

## Enable Cache Deception Armor

To enable Cache Deception Armor, you need to start by creating a [cache rule](/cache/how-to/cache-rules/). Follow the steps below for guidance:

1. In the Cloudflare dashboard, go to the **Cache Rules** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

2. Select **Create rule**.
3. Under **When incoming requests match**, define the [rule expression](/ruleset-engine/rules-language/expressions/edit-expressions/#expression-builder).
4. Under **Then**, in the **Cache eligibility** section, select **Eligible for cache**.
5. Add the **Cache Key** setting to the rule and turn on **Cache deception armor**.
6. To save and deploy your rule, select **Deploy**. If you are not ready to deploy your rule, select **Save as Draft**.

# Cross-Origin Resource Sharing (CORS)

A cross-origin request occurs when a webpage on one origin (for example, `a.example.com`) requests a resource from a different origin (for example, `b.secondexample.com`). Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to let the server at `b.secondexample.com` indicate whether `a.example.com` is allowed to access its resources. Browsers enforce these headers and block access to responses that are not permitted.

Cloudflare supports CORS by:

* Identifying cached assets based on the `Host` Header, `Origin` Header, URL path, and query. This allows different resources to use the same `Host` header but different `Origin` headers.
* Passing `Access-Control-Allow-Origin` headers from the origin server to the browser.

The `Access-Control-Allow-Origin` header lets a server specify rules for sharing its resources with external origins. A server may respond with different `Access-Control-Allow-Origin` values depending on the `Origin` header in the request. These headers are often present on [cacheable content](/cache/concepts/default-cache-behavior/).

## Add or change CORS headers at the origin server

If you add or change CORS configuration at your origin web server, purging the Cloudflare cache by URL does not update the CORS headers. Force Cloudflare to retrieve the new CORS headers via one of the following options:

* Change the filename or URL to bypass cache to instruct Cloudflare to retrieve the latest CORS headers.
* Use the [single-file purge API](/api/resources/cache/methods/purge/#purge-cached-content-by-url) to specify the appropriate CORS headers along with the purge request.
* Update the resource’s last-modified time at your origin web server. Then, complete a [full purge](/cache/how-to/purge-cache/purge-everything/) to retrieve the latest version of your assets including updated CORS headers.

## Add or change CORS headers on Cloudflare

You can use one of following methods to set CORS headers using Cloudflare products:

- Use a [Worker](/workers/): Refer to [CORS header proxy](/workers/examples/cors-header-proxy/) for an example.
- Configure a [Snippet](/rules/snippets/): Refer to [Define CORS headers](/rules/snippets/examples/define-cors-headers/) for an example.
- Use [Transform Rules](/rules/transform/): Refer to [Add a wildcard CORS response header](/rules/transform/examples/add-cors-header/) for an example.

# Cache security

import { DirectoryListing } from "~/components"

Review the following content to learn more about cache security.

<DirectoryListing />

# Head Requests and Set-Cookie Headers

This page describes how Cloudflare's cache system behaves in interaction with:

* `HEAD` requests
* `Set-Cookie` response headers

## Interaction of `HEAD` requests with Cache

Cloudflare converts `HEAD` requests to `GET` requests for [cacheable requests](/cache/concepts/default-cache-behavior/#default-cached-file-extensions).

When you make a `HEAD` request for a cacheable resource and Cloudflare does not have that resource in the edge cache, a cache miss happens. Cloudflare will send a `GET` request to your origin, cache the full response and return the response headers only. Make sure the origin server is set up to handle `GET` requests, even if only `HEAD` requests are expected, so that compatibility with this behavior is ensured.

## Interaction of `Set-Cookie` response header with Cache

For non-cacheable requests, `Set-Cookie` is always preserved. For cacheable requests, there are three possible behaviors:

* `Set-Cookie` is returned from origin and the default cache level is used. If [origin cache control](/cache/concepts/cache-control/) is not enabled, Cloudflare removes the `Set-Cookie` and caches the asset. If origin cache control is enabled, Cloudflare does not cache the asset and preserves the `Set-Cookie`. A cache status of `BYPASS` is returned.

* `Set-Cookie` is returned from origin and the cache level is set to `Cache Everything` in Page Rules, or `Eligible for cache` in Cache Rules. In this case, Cloudflare preserves the `Set-Cookie` but does not cache the asset. A cache `MISS` will be returned every time.

* `Set-Cookie` is returned from origin, the cache level is set to `Cache Everything` in Page Rules, or `Eligible for cache` in Cache Rules, and edge cache TTL is explicitly set using either the "Ignore cache-control header and use this TTL" or "Status code TTL" setting. In this case, Cloudflare removes the `Set-Cookie` and the asset is cached.

# Origin Cache Control

import { Details } from "~/components"

Origin Cache Control is a Cloudflare feature. When enabled on an Enterprise customer's website, it indicates that Cloudflare should strictly respect `Cache-Control` directives received from the origin server. Free, Pro and Business customers have this feature enabled by default.

`Cache-Control` directives in the HTTP response from your origin server provide specific [caching instructions](https://datatracker.ietf.org/doc/html/rfc7234) to intermediary services like Cloudflare.

With the Origin Cache Control feature enabled, `Cache-Control` directives present in the origin server's response will be followed as specified. For example, if the response includes a `max-age` directive of 3,600 seconds, Cloudflare will cache the resource for that duration before checking the origin server again for updates.

Cloudflare's [Cache Rules](/cache/how-to/cache-rules/) allows users to either augment or override an origin server's `Cache-Control` headers or [default policies](/cache/concepts/default-cache-behavior/) set by Cloudflare.

The following sections cover:

* The most common `Cache-Control` directives.
* How to enable Origin Cache Control.
* How Origin Cache Control behaves with `Cache-Control` directives.
* How other Cloudflare products interact with `Cache-Control` directives.

## `Cache-control` directives

A `Cache-Control` header can include a number of directives, and the directive dictates who can cache a resource along with how long those resources can be cached before they must be updated.

:::note[Note]


For more information about `Cache-Control` directives at origin servers, refer to the [Mozilla Cache-Control documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control).


:::

If multiple directives are passed together, each directive is separated by a comma. If the directive takes an argument, it follows the directive separated by an equal sign. For example: `max-age=86400`.

Directives can be broken down into four groups: [cacheability](/cache/concepts/cache-control/#cacheability), [expiration](/cache/concepts/cache-control/#expiration), [revalidation](/cache/concepts/cache-control/#revalidation), and [other](/cache/concepts/cache-control/#other).

### Cacheability

Cacheability refers to whether or not a resource should enter a cache, and the directives below indicate a resource's cacheability.

* `public` — Indicates any cache may store the response, even if the response is normally non-cacheable or cacheable only within a private cache.
* `private` — Indicates the response message is intended for a single user, such as a browser cache, and must not be stored by a shared cache like Cloudflare or a corporate proxy.
* `no-store` — Indicates any cache, such as a client or proxy cache, must not store any part of either the immediate request or response.

### Expiration

Expiration refers to how long a resource should remain in the cache, and the directives below affect how long a resource stays in the cache.

:::note[Note]


Cloudflare respects whichever value is higher: the [Browser Cache TTL](/cache/how-to/edge-browser-cache-ttl/) in Cloudflare or the `max-age` header. You can also simultaneously specify a Cloudflare Edge Cache TTL different than a Browser's Cache TTL respectively via the `s-maxage` and `max-age` `Cache-Control` headers.

When using Origin Cache Control and setting `max-age=0`, Cloudflare prefers to cache and revalidate. With Origin Cache Control off and `max-age=0`, Cloudflare will bypass cache.

When setting `no-cache` with Origin Cache Control off, Cloudflare does not cache. When setting `no-cache` with Origin Cache Control on, Cloudflare caches and always revalidates.


:::

* `max-age=seconds` — Indicates the response is stale after its age is greater than the specified number of seconds. Age is defined as the time in seconds since the asset was served from the origin server. The `seconds` argument is an unquoted integer.
* `s-maxage=seconds` — Indicates that in shared caches, the maximum age specified by this directive overrides the maximum age specified by either the `max-age` directive or the `Expires` header field. The `s-maxage` directive also implies the semantics of the `proxy-revalidate` response directive. Browsers ignore `s-maxage`.

:::caution[`s-maxage` disables `stale-while-revalidate`]
Per [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-4), `s-maxage` incorporates the semantics of `proxy-revalidate`, which means a shared cache must not serve stale content without first revalidating with the origin. For a complete list of directives that disable `stale-while-revalidate` and workarounds, refer to [Controlling stale behavior](/cache/concepts/revalidation/#controlling-stale-behavior).
:::
* `no-cache` — Indicates the response cannot be used to satisfy a subsequent request without successful validation on the origin server. This allows an origin server to prevent a cache from using the origin to satisfy a request without contacting it, even by caches that have been configured to send stale responses.

Ensure the HTTP `Expires` header is set in your origin server to use Greenwich Mean Time (GMT) as stipulated in [RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 "3.3.1 Full Date").

### Revalidation

Revalidation determines how the cache should behave when a resource expires, and the directives below affect the revalidation behavior.

* `must-revalidate` — Indicates that once the resource is stale, a cache (client or proxy) must not use the response to satisfy subsequent requests without successful validation on the origin server.
* `proxy-revalidate` — Has the same meaning as the `must-revalidate` response directive except that it does not apply to private client caches.
* `stale-while-revalidate=<seconds>` — When present in an HTTP response, indicates caches may serve the response in which it appears after it becomes stale, up to the indicated number of seconds since the resource expired. If [Always Online](/cache/how-to/always-online/) is enabled, then the `stale-while-revalidate` and `stale-if-error` directives are ignored. This directive is not supported when using the Cache API methods `cache.match` or `cache.put`. For more information, refer to the [Workers documentation for Cache API](/workers/runtime-apis/cache/#methods).


:::note
`stale-while-revalidate` is now fully asynchronous. The first request after expiry triggers revalidation in the background and immediately receives stale content with an UPDATING status instead of blocking. All requests during revalidation are served stale with an UPDATING status until the origin responds, after which they receive a HIT.

For more details, refer to [Revalidation](/cache/concepts/revalidation/#asynchronous-revalidation).
:::

* `stale-if-error=<seconds>` — Indicates that when an error is encountered, a cached stale response may be used to satisfy the request, regardless of other freshness information. To avoid this behavior, include `stale-if-error=0` directive with the object returned from the origin. This directive is not supported when using the Cache API methods `cache.match` or `cache.put`. For more information, refer to the [Workers documentation for Cache API](/workers/runtime-apis/cache/#methods).

:::note[Error status codes]
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 origin response replaces the stale cached content. There is currently no way to customize which status codes trigger `stale-if-error`.
:::

The `stale-if-error` directive is ignored if [Always Online](/cache/how-to/always-online/) is enabled or if an explicit in-protocol directive is passed. Examples of explicit in-protocol directives include a `no-store` or `no-cache cache` directive, a `must-revalidate` cache-response-directive, or an applicable `s-maxage` or `proxy-revalidate` cache-response-directive.

### Other

Additional directives that influence cache behavior are listed below.

* `no-transform` — Indicates that an intermediary — regardless of whether it implements a cache — must not transform the payload.
* `vary` — By default, Cloudflare does not consider vary values in caching decisions. Vary values are respected when you configure the [Cache Rules Vary setting](/cache/concepts/vary/), when [Vary for images](/cache/advanced-configuration/vary-for-images/) is configured, and when the vary header is [`vary: accept-encoding`](/speed/optimization/content/compression/).
* `immutable` — Indicates to clients the response body does not change over time. The resource, if unexpired, is unchanged on the server. The user should not send a conditional revalidation request, such as `If-None-Match` or `If-Modified-Since`, to check for updates, even when the user explicitly refreshes the page. This directive has no effect on public caches like Cloudflare, but does change browser behavior.

### Understand `no-store` and `no-cache` directives

There is often confusion between the directives `Cache-Control: no-store` and `Cache-Control: no-cache`, particularly regarding how they impact browser caching and features like the [Back-Forward Cache](https://developer.mozilla.org/en-US/docs/Glossary/bfcache) (BFCache).

#### `no-store`

- Tells both browsers and intermediaries (like CDNs) not to store a copy of the response under any circumstance.
- The response is never written to disk or memory, which means the browser must fetch it again every time.
- In many browsers, `no-store` disables BFCache, because restoring a page from BFCache requires the browser to keep a copy of the page's memory state, which contradicts the “do not store” directive.
- This directive is used for highly sensitive or dynamic data (for example, banking apps, personal information, secure dashboards).

#### `no-cache`

- Allows storing of the response (in both browser and intermediate caches), but requires revalidation with the origin server before using it.
- This ensures the content is always up-to-date, while still potentially allowing BFCache or other forms of performance optimization.
- This directive is used for data that changes frequently but is not sensitive, and can be served faster if validated rather than re-downloaded.

For more information about how these directives behave when Origin Cache Control is enabled or disabled refer to the [Directives](/cache/concepts/cache-control/#directives) section.

## Enable Origin Cache Control

If you enable Origin Cache Control, Cloudflare will aim to strictly adhere to [RFC 7234](https://datatracker.ietf.org/doc/html/rfc7234). Enterprise customers have the ability to select if Cloudflare will adhere to this behavior, enabling or disabling Origin Cache Control for their websites through cache rules in the [dashboard](/cache/how-to/cache-rules/settings/#origin-cache-control-enterprise-only) or via [API](/cache/how-to/cache-rules/settings/#origin-cache-control-enterprise-only). Free, Pro, and Business customers have this option enabled by default and cannot disable it.

## Origin Cache Control behavior

The following section covers the directives and behavioral conditions associated with enabling or disabling Origin Cache Control.

:::caution[Integer values required]
The `max-age`, `s-maxage`, and `stale-while-revalidate` directives require integer values per RFC 9111. Floating-point values (for example, `max-age=2.5`) are not valid and will be ignored, potentially causing cache bypass. Ensure your origin returns integer TTL values.
:::

### Directives

The table below lists directives and their behaviors when Origin Cache Control is disabled and when it is enabled.

| Directive                 | Origin Cache Control Disabled Behavior            | Origin Cache Control Enabled Behavior                                                                                                           |
|---------------------------|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `s-maxage=0`              | Will not cache.                                   | Caches and always revalidates                                                                                                                   |
| `max-age=0`               | Will not cache.                                   | Caches and always revalidates.                                                                                                                  |
| `no-cache`                | Will not cache.                                   | Caches and always revalidates. Does not serve stale.                                                                                            |
| `no-cache=<headers>`      | Will not cache.                                   | Caches if headers mentioned in `no-cache=<headers>` do not exist. Always revalidates if any header mentioned in `no-cache=<headers>` is present.|
| `Private=<headers>`       | Will not cache.                                   | Does not cache `<headers>` values mentioned in `Private=<headers>` directive.                                                                   |
| `must-revalidate`         | Cache directive is ignored and stale is served.   | Does not serve stale. Must revalidate for CDN and for browser.                                                                                  |
| `proxy-revalidate`        | Cache directive is ignored and stale is served.   | Does not serve stale. Must revalidate for CDN but not for browser.                                                                              |
| `no-transform`            | May (un)Gzip, Polish, email filter, etc.          | Does not transform body.                                                                                                                        |
| `s-maxage=delta, delta>1` | Same as `max-age`.                                | `Max-age` and `proxy-revalidate`.                                                                                                               |
| `immutable`               | Not proxied downstream.                           | Proxied downstream. Browser facing, does not impact caching proxies.                                                                            |
| `no-store`                | Will not cache.                                   | Will not cache.                                                                                                                                  |

### Conditions

Certain scenarios also affect Origin Cache Control behavior when it is enabled or disabled.

<table>
  <tbody>
    <th colspan="5" rowspan="1">
      Condition
    </th>
    <th colspan="5" rowspan="1">
      Origin Cache Control disabled behavior
    </th>
    <th colspan="5" rowspan="1">
      Origin Cache Control enabled behavior
    </th>
    <tr>
      <td colspan="5" rowspan="1">
        Presence of <code>Authorization</code> header.
      </td>
      <td colspan="5" rowspan="1">
        Content may be cached.
      </td>
      <td colspan="5" rowspan="1">
        Content is cached only if <code>must-revalidate</code>, <code>public</code>, or <code>s-maxage</code> is also present.
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Use of <code>no-cache</code> header.
      </td>
      <td colspan="5" rowspan="1">
        In logs, <code>cacheStatus=miss</code>.
      </td>
      <td colspan="5" rowspan="1">
        In logs, <code>cacheStatus=bypass</code>.
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Origin response has <code>Set-Cookie</code> header and default cache level is used.
      </td>
      <td colspan="5" rowspan="1">
        Content may be cached with stripped <code>set-cookie</code> header.
      </td>
      <td colspan="5" rowspan="1">
        Content is not cached.
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Browser Cache TTL is set.
      </td>
      <td colspan="5" rowspan="1">
        `Cache-Control` returned to eyeball does not include <code>private</code>.
      </td>
      <td colspan="5" rowspan="1">
        If origin returns <code>private</code> in `Cache-Control` then preserve it.
      </td>
    </tr>
  </tbody>
</table>

:::note
When the `Cloudflare-Cdn-Cache-Control` header is set, OCC is turned **on** (regardless of whether OCC is enabled or disabled). As a result, we apply our Authorization header logic (per [RFC 7234, Section 3.2](https://tools.ietf.org/html/rfc7234#section-3.2)) to allow only the `s-maxage`, `must-revalidate`, or `public` directives. If any other directive is present, we do not cache the asset and instead return `BYPASS`.
:::

## Examples

Review the examples below to learn which directives to use with the `Cache-Control` header to control specific caching behavior.


<Details header="Cache a static asset.">

`Cache-Control: public, max-age=86400`


</Details>


<Details header="Ensure a secret asset is never cached.">

`Cache-Control: no-store`


</Details>


<Details header="Cache assets on browsers but not on proxy cache.">

`Cache-Control: private, max-age=3600`


</Details>


<Details header="Cache assets in client and proxy caches, but prefer revalidation when serve.">

`Cache-Control: public, no-cache`


</Details>


<Details header="Cache assets in proxy caches but REQUIRE revalidation by the proxy when serve.">

`Cache-Control: public, no-cache, proxy-revalidate` or `Cache-Control: public, s-maxage=0`


</Details>


<Details header="Cache assets in proxy caches, but REQUIRE revalidation by any cache when serve.">

`Cache-Control: public, no-cache, must-revalidate`


</Details>


<Details header="Cache assets, but ensure the proxy does not modify it.">

`Cache-Control: public, no-transform`

This configuration also disables transformation like gzip or brotli compression from our edge to your visitors if the original payload was served uncompressed.


</Details>


<Details header="Cache assets with revalidation, but allow stale responses if origin server is unreachable.">

`Cache-Control: public, max-age=3600, stale-if-error=60`

With this configuration, Cloudflare attempts to revalidate the content with the origin server after it has been in cache for 3600 seconds (one hour). If the server returns an error instead of proper revalidation responses, Cloudflare continues serving the stale resource for a total of one minute beyond the expiration of the resource.


</Details>


<Details header="Cache assets for different amounts of time on Cloudflare and in visitor browsers.">

`Cache-Control: public, max-age=7200, s-maxage=3600`


</Details>


<Details header="Cache an asset and serve while asset is being revalidated.">

`Cache-Control: max-age=600, stale-while-revalidate=30`

This configuration indicates the asset is fresh for 600 seconds. The asset can be served stale for up to an additional 30 seconds while Cloudflare revalidates the asset with the origin in the background. For more information, refer to [Revalidation](/cache/concepts/revalidation/).

:::note
Do not use `s-maxage` with `stale-while-revalidate`. The `s-maxage` directive implies `proxy-revalidate`, which prevents shared caches from serving stale content. For workarounds, refer to [Controlling stale behavior](/cache/concepts/revalidation/#controlling-stale-behavior).
:::


</Details>

## Interaction with other Cloudflare features

This section covers how other Cloudflare features interact with `Cache-Control` directives.

### Edge Cache TTL

[Edge Cache TTL](/cache/how-to/edge-browser-cache-ttl/#edge-cache-ttl) Cache Rules override `s-maxage` and disable revalidation directives if present. When Origin Cache Control is enabled at Cloudflare, the original `Cache-Control` header passes downstream from our edge even if Edge Cache TTL overrides are present. Otherwise, when Origin Cache Control is disabled at Cloudflare, Cloudflare overrides the Origin Cache Control.

### Browser Cache TTL

[Browser Cache TTL](/cache/how-to/edge-browser-cache-ttl/#browser-cache-ttl) Cache Rules override `max-age` settings passed downstream from our edge, typically to your visitor's browsers.

### Polish

[Polish](/images/polish/) is disabled when the `no-transform` directive is present.

### Gzip and Other Compression

Compression is disabled when the `no-transform` directive is present. If the original asset fetched from the origin is compressed, it is served compressed to the visitor. If the original asset is uncompressed, compression is not applied.

### JavaScript Detections

[JavaScript Detections](/cloudflare-challenges/challenge-types/javascript-detections/) injection is disabled when the `no-transform` directive is present. The `cf.bot_management.js_detection.passed` field will show as `missing` for affected requests.

# Cloudflare cache responses

The `CF-Cache-Status` header output indicates whether a resource is cached or not. To investigate cache responses returned by this header, use services like [Redbot](https://redbot.org/), [webpagetest.org](http://www.webpagetest.org/), or a visual tool like [Cloudflare Optics plugin](https://chromewebstore.google.com/detail/cloudflare-optics/mdjgbjnbdnhneejmmaabmccfehigbjbe).

:::note[Age response header]
The `Age` response header is returned when Cloudflare serves a response from cache. It is the number of seconds an asset has been in Cloudflare's cache since it was admitted or last revalidated, and it resets on revalidation against the origin, on purge, or on eviction. With [Tiered Cache](/cache/how-to/tiered-cache/), `Age` reflects the object's age in Cloudflare's network-wide cache — a `HIT` served locally can carry an `Age` inherited from an upper tier, so the value can be older than the last local fill.

Cloudflare sets `Age` on `HIT`, `STALE`, and `UPDATING` responses. It does not set `Age` on `MISS`, on `EXPIRED` or `REVALIDATED` when the request revalidated against the origin, on `DYNAMIC`, `BYPASS`, or `NONE`/`UNKNOWN`. If the origin sets `Age` on an uncacheable response, that value is proxied to the client unchanged — a `DYNAMIC` or `BYPASS` response can carry an `Age` that came from the origin, not from Cloudflare.
:::

Below you can find a comprehensive breakdown of Cloudflare's cache response statuses.

## HIT

The resource was found in Cloudflare's cache.

## MISS

The response is eligible for cache but was not present in Cloudflare's cache at request time, so it was served from the origin web server. Responses that Cloudflare chooses not to cache return [`BYPASS`](#bypass) instead of `MISS`.

## NONE/UNKNOWN

Cloudflare generated a response that denotes the asset is not eligible for caching. This may have happened because:

* A Worker generated a response without sending any subrequests. In this case, the response did not come from cache, so the cache status will be `none/unknown`.

* A Worker request made a subrequest (`fetch`). In this case, the subrequest will be logged with a cache status, while the main request will be logged with `none/unknown` status (the main request did not hit cache, since Workers sits in front of cache).

* A WAF custom rule was triggered to block a request. The response will come from the Cloudflare global network before it hits cache. Since there is no cache status, Cloudflare will log as `none/unknown`.

* A [redirect rule](/rules/url-forwarding/) or [Always Use HTTPS](/ssl/edge-certificates/additional-options/always-use-https/) caused the global network to respond with a redirect to another asset/URL. This redirect response happens before the request reaches cache, so the cache status is `none/unknown`.

## EXPIRED

The resource was found in Cloudflare's cache but was expired and served from the origin web server.

## STALE

The resource was served from Cloudflare's cache but was expired. Cloudflare could not contact the origin web server to retrieve an updated resource.

## BYPASS

Cloudflare considered the asset eligible for cache at request time — either because it matches the [default cached file extensions](/cache/concepts/default-cache-behavior/#default-cached-file-extensions), or because a [Cache Rule](/cache/how-to/cache-rules/) enabled caching for it — but the origin response was ultimately not cacheable.

Common reasons the origin response is treated as not cacheable include:

* The response exceeds the [maximum cacheable file size](/cache/concepts/default-cache-behavior/#cacheable-size-limits) for your plan.
* The origin returned `no-store` or bare `private` in a `Cloudflare-CDN-Cache-Control` or `CDN-Cache-Control` header. Cloudflare evaluates these headers ahead of `Cache-Control`, in the precedence `Cloudflare-CDN-Cache-Control` > `CDN-Cache-Control` > `Cache-Control` — so an origin returning `Cache-Control: public, max-age=3600` together with `CDN-Cache-Control: no-store` produces `BYPASS`. A [Cache Rule](/cache/how-to/cache-rules/) with an [Edge Cache TTL](/cache/how-to/cache-rules/settings/#edge-ttl) setting that ignores origin cache-control overrides these directives, same as it does for `Cache-Control: no-store`. `no-cache`, `max-age=0`, or `s-maxage=0` in these headers do not produce `BYPASS`. They produce `MISS` on the first request, then [`REVALIDATED`](#revalidated) or [`EXPIRED`](#expired). Refer to [CDN-Cache-Control](/cache/concepts/cdn-cache-control/) for the precedence rules.
* The origin returned `Cache-Control: no-store` or `private`. These directives prevent caching in either [Origin Cache Control](/cache/concepts/cache-control/) mode.
* The origin returned `Cache-Control: no-cache`, `max-age=0`, or `s-maxage=0`, and [Origin Cache Control](/cache/concepts/cache-control/) is disabled (the default on Enterprise plans). With Origin Cache Control enabled (the default on Free, Pro, and Business plans), these directives cause Cloudflare to cache and revalidate the response instead, producing [`REVALIDATED`](#revalidated) or [`EXPIRED`](#expired). Refer to [Understand `no-store` and `no-cache` directives](/cache/concepts/cache-control/#understand-no-store-and-no-cache-directives) and the [Conditions](/cache/concepts/cache-control/#conditions) table.
* The origin returned a `Set-Cookie` header. Refer to [Interaction of `Set-Cookie` response header with Cache](/cache/concepts/cache-behavior/#interaction-of-set-cookie-response-header-with-cache) for the specific configurations that produce `BYPASS`.
* The origin returned a `Vary: *` response header, which always bypasses cache.
* The request included an `Authorization` header and [Origin Cache Control](/cache/concepts/cache-control/) is enabled (the default on Free, Pro, and Business plans). In that mode, the response is cacheable only if `Cache-Control` also includes `public`, `s-maxage`, or `must-revalidate`. On Enterprise plans with Origin Cache Control disabled, `Authorization` does not by itself prevent caching.

:::note
If you configured a [Cache Rule](/cache/how-to/cache-rules/) with **Eligible for cache** set to *Yes* (for example, on HTML content) and the origin returns a non-cacheable `Cache-Control` directive, the response is returned with `CF-Cache-Status: BYPASS` — not `DYNAMIC`. `DYNAMIC` is only returned when Cloudflare determines the asset is not eligible for cache at request time.
:::

BYPASS means the decision not to cache was made at **response time** — the request was initially eligible for caching, but the origin response or response headers instructed Cloudflare not to cache. For example, a Cache Rule that sets `"cache": true` enables caching at request time, but if the origin returns `Cache-Control: no-store`, the response will be BYPASS.

If you expected a URL to be cached but see `BYPASS`, refer to [Investigate uncached responses](/cache/troubleshooting/investigating-uncached-responses/#bypass--origin-response-is-not-cacheable) for a step-by-step diagnostic.

## REVALIDATED

The origin confirmed the cached resource was unchanged via a conditional request (`If-Modified-Since` or `If-None-Match`), and the response is served from Cloudflare's cache. This status reflects the synchronous validation path — the request waits for the origin to respond before being served.

With [asynchronous `stale-while-revalidate`](/cache/concepts/revalidation/#asynchronous-revalidation), most revalidations now return `UPDATING` or `HIT` instead. `REVALIDATED` is seen in the following situations: `stale-while-revalidate` is not set; or directives like `must-revalidate` or `no-cache` (with [Origin Cache Control](/cache/concepts/cache-control/) enabled) prevent stale content from being served.

## UPDATING

The resource was expired but served from Cloudflare's cache while the origin updates it in the background. `UPDATING` is the expected status during [asynchronous `stale-while-revalidate`](/cache/concepts/revalidation/#asynchronous-revalidation) revalidation — all requests during the revalidation window receive `UPDATING` or `HIT` rather than waiting for the origin.

## DYNAMIC

Cloudflare determined at request time that the asset is not eligible for cache, so the request went to the origin web server without a cache lookup.

This typically happens when:

* The requested asset is not one of the [default cached file extensions](/cache/concepts/default-cache-behavior/#default-cached-file-extensions) (for example, HTML or JSON) and no rule instructs Cloudflare to cache it.
* A [Cache Rule](/cache/how-to/cache-rules/) with the **Bypass cache** setting matches the request. The legacy `Cache Level: Bypass` option in [Configuration Rules](/rules/configuration-rules/) or [Page Rules](/rules/page-rules/) behaves the same way.
* [Development Mode](/cache/reference/development-mode/) is enabled on the zone, which suspends cache for three hours.

Use [Cache Rules](/cache/how-to/cache-rules/) to change what content Cloudflare caches. Once the request is treated as eligible for cache, the `CF-Cache-Status` header will reflect the response-time cache decision (`HIT`, `MISS`, `EXPIRED`, `REVALIDATED`, `BYPASS`, and so on) — refer to [BYPASS](#bypass) for the case where the origin response is ultimately not cacheable.

If you expected the request to be eligible for cache but see `DYNAMIC`, refer to [Investigate uncached responses](/cache/troubleshooting/investigating-uncached-responses/#dynamic--request-not-eligible-for-cache).

# CDN-Cache-Control

`CDN-Cache-Control` is a response header field set on the origin to separately control the behavior of CDN caches from other intermediaries that might handle a response. You can set the `CDN-Cache-Control` or `Cloudflare-CDN-Cache-Control` response header using the same directives used with the [Cache-Control](/cache/concepts/cache-control/).

## Header precedence

You have several options available to determine how `CDN-Cache-Control` directives interact with `Cache-Control` directives.

If a [Cache Response Rule](/cache/how-to/cache-response-rules/) sets `Cache-Control` directives using the `set_cache_control` action, those directives take precedence over any origin-set `Cloudflare-CDN-Cache-Control` and `CDN-Cache-Control` headers.

When no Cache Response Rule applies, an origin can:

* Return the `CDN-Cache-Control` response header which Cloudflare evaluates to make caching decisions. `Cache-Control`, if also returned by the origin, is proxied as is and does not affect caching decisions made by Cloudflare. Additionally, `CDN-Cache-Control` is proxied downstream in case there are other CDNs between Cloudflare and the browser.

* Return the `Cloudflare-CDN-Cache-Control` response header. This results in the same behavior as the origin returning `CDN-Cache-Control` except Cloudflare does not proxy `Cloudflare-CDN-Cache-Control` downstream because it’s a header only used to control Cloudflare. This option is beneficial if you want only Cloudflare to have a different caching behavior while all other downstream servers rely on `Cache-Control` or if you do not want Cloudflare to proxy the `CDN-Cache-Control` header downstream.

* Return both `Cloudflare-CDN-Cache-Control` and `CDN-Cache-Control` response headers. In this case, Cloudflare only looks at `Cloudflare-CDN-Cache-Control` when making caching decisions because it is the most specific version of `CDN-Cache-Control` and proxies `CDN-Cache-Control` downstream. Only forwarding `CDN-Cache-Control` in this situation is beneficial if you want Cloudflare to have a different caching behavior than other CDNs downstream.

Additionally, surrogates will not honor `Cache-Control` headers in the response from an origin. For example, if the `Surrogate-Control` header is present within the response, Cloudflare ignores any `Cache-Control` directives, even if the `Surrogate-Control` header does not contain directives.

## Interaction with other Cloudflare features

### Edge Cache TTL cache rule

The [Edge Cache TTL cache rule](/cache/how-to/cache-rules/settings/#edge-ttl) overrides the amount of time an asset is cached on the edge (Cloudflare data centers). This cache rule overrides directives in `Cloudflare-CDN-Cache-Control/CDN-Cache-Control` which manage how long an asset is cached on the edge. You can create this rule in the dashboard in **Caching** > **Cache Rules**.

### Browser Cache TTL cache rule

The [Browser Cache TTL cache rule](/cache/how-to/cache-rules/settings/#browser-ttl) overrides the amount of time an asset is cached by browsers/servers downstream of Cloudflare. Browser Cache TTL only modifies the `Cache-Control` response header. This cache rule does not modify `Cloudflare-CDN-Cache-Control/CDN-Cache-Control` response headers.

### Other Origin Response Headers

The origin returns the `Expires` response header which specifies the amount of time before an object is considered stale to the browser. This response header does not affect the caching decision at Cloudflare when `Cloudflare-CDN-Cache-Control/CDN-Cache-Control` is in use.

### Cloudflare Default cache values

In situations where Cloudflare does not receive `Cloudflare-CDN-Cache-Control`, `CDN-Cache-Control`, or `Cache-Control` values, cacheable assets use the general [default values](/cache/concepts/default-cache-behavior/).

## When to use CDN-Cache-Control

### Manage cached assets TTLs

Use `CDN-Cache-Control` when you want to manage cached asset’s TTLs separately for origin caches, CDN caches, and browser caches. The example below shows how you can manage your cached asset’s TTLs using origin-set response headers.

Headers:

* `Cache-Control: max-age=14400, s-maxage=84000`
* `Cloudflare-CDN-Cache-Control: max-age=24400`
* `CDN-Cache-Control: max-age=18000`

Cache behavior:

<table>
  <tbody>
    <th colspan="5" rowspan="1">
      Caches
    </th>
    <th colspan="5" rowspan="1">
      Cache TTL (seconds)
    </th>
    <tr>
      <td colspan="5" rowspan="1">
        Origin Server Cache
      </td>
      <td colspan="5" rowspan="1">
        14400
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Network Shared Cache
      </td>
      <td colspan="5" rowspan="1">
        84000
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Cloudflare Edge
      </td>
      <td colspan="5" rowspan="1">
        24400
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Other CDNs
      </td>
      <td colspan="5" rowspan="1">
        18000
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Browser Cache
      </td>
      <td colspan="5" rowspan="1">
        14400
      </td>
    </tr>
  </tbody>
</table>

### Specify when to serve stale content

Use `CDN-Cache-Control` headers in conjunction with `Cache-Control` headers to specify when to serve stale content in the case of error or during revalidation. The example below shows how you might set your headers and directives to apply to CDNs when handling errors.

Headers:

* `Cache-Control: stale-if-error=400`
* `Cloudflare-CDN-Cache-Control: stale-if-error=60`
* `CDN-Cache-Control: stale-if-error=200`

Behavior in response to [5XX error](/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/):

<table>
  <tbody>
    <th colspan="5" rowspan="1">
      Caches
    </th>
    <th colspan="5" rowspan="1">
      Stale served (seconds) in response to error
    </th>
    <tr>
      <td colspan="5" rowspan="1">
        Origin Cache Layer/Network Cache/Browser Cache
      </td>
      <td colspan="5" rowspan="1">
        400 (if it assumes the directive applies)
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Cloudflare Edge
      </td>
      <td colspan="5" rowspan="1">
        60
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        Other CDN
      </td>
      <td colspan="5" rowspan="1">
        200
      </td>
    </tr>
  </tbody>
</table>

# Customize cache

Some possible combinations of origin web server settings and Cloudflare [Cache Rules](/cache/how-to/cache-rules/) include:

## Create a directory for static content at your origin web server

For example, create a `/static/` subdirectory at your origin web server and a Cache Everything Cache Rule matching the following expression:

* Using the Expression Builder: `Hostname contains "example.com" AND URI Path starts with "/static"`
* Using the Expression Editor: `(http.host contains "example.com" and starts_with(http.request.uri.path, "/static"))`

## Append a unique file extension to static pages

For example, create a `.shtml` file extension for resources at your origin web server and a Cache Everything Cache Rule matching the following expression:

* Using the Expression Builder: `Hostname contains "example.com" AND URI Path ends with ".shtml"`
* Using the Expression Editor: `(http.host contains "example.com" and ends_with(http.request.uri.path, ".shtml"))`

## Add a query string to a resource’s URL to mark the content as static

For example, add a `static=true` query string for resources at your origin web server and a Cache Everything Cache Rule matching the following expression:

* Using the Expression Builder: `Hostname contains "example.com" AND URI Query String contains "static=true"`
* Using the Expression Editor: `(http.host contains "example.com" and http.request.uri.query contains "static=true")`

Resources that match a Cache Everything Cache Rule are still not cached if the origin web server sends a Cache-Control header of `max-age=0`, `private`, `no-cache`, or an `Expires` header with an already expired date. Include the [Edge Cache TTL](/cache/how-to/cache-rules/settings/#edge-ttl) setting within the Cache Everything Cache Rule to additionally override the `Cache-Control` headers from the origin web server.

# Default cache behavior

import { GlossaryTooltip, Render, DashButton } from "~/components"

Cloudflare respects the origin web server’s cache headers in the following order unless an [Edge Cache TTL cache rule](/cache/how-to/cache-rules/settings/#edge-ttl) overrides the headers. Refer to the [Edge TTL](/cache/how-to/configure-cache-status-code/#edge-ttl) section for details on default TTL behavior.

* Cloudflare **does not** cache the resource when:
  * The `Cache-Control` header is set to `private`, `no-store`, `no-cache`, or `max-age=0`.
  * The [Set-Cookie header](/cache/concepts/cache-behavior/#interaction-of-set-cookie-response-header-with-cache) exists.
  * The HTTP request method is anything other than a `GET`.
* Cloudflare **does** cache the resource when:
  * The `Cache-Control` header is set to `public` and `max-age` is greater than 0.
  * The `Expires` header is set to a future date.

:::note
Cloudflare does cache the resource even if there is no `Cache-Control` header based on [status codes](/cache/how-to/configure-cache-status-code/#edge-ttl).
:::

:::note
If both `max-age` and an `Expires` header are set, `max-age` will be used by Cloudflare.
:::

When [Origin Cache Control](/cache/concepts/cache-control/) is enabled on an Enterprise customer’s website, it indicates that Cloudflare should strictly respect `Cache-Control` directives received from the origin server. Free, Pro and Business customers have this feature enabled by default. For a list of directives and behaviors when Origin Cache-Control is enabled or disabled, refer to [Cache-Control directives](/cache/concepts/cache-control/#cache-control-directives).

## Client-side range requests

Clients can use the HTTP `Range` header to request part of a file. Cloudflare can serve these requests from complete or partial cached files. Response behavior depends on the request method, range syntax, conditional headers, content encoding, and cache eligibility.

For complete response and origin requirements, refer to [Range request behavior](/cache/reference/range-requests/).

## Request collapsing

When multiple requests arrive simultaneously at a single Cloudflare data center for the same asset that is not in cache (a cache miss), Cloudflare uses a <GlossaryTooltip term="cache lock">cache lock</GlossaryTooltip> to avoid sending duplicate requests to your origin. Only the first request is forwarded to the origin to fetch the asset. The remaining requests wait for the first request to complete, after which the response is [streamed](https://blog.cloudflare.com/introducing-concurrent-streaming-acceleration/) to all waiting requests.

The cache lock ensures that Cloudflare only sends one request at a time to the origin for a given asset from a single location in Cloudflare's network, preventing the origin from receiving excessive traffic.

## Default cached file extensions

Cloudflare only caches based on file extension and not by MIME type. The Cloudflare CDN does not cache HTML or JSON by default. Additionally, by default Cloudflare caches a website's robots.txt.

|       |      |      |      |      |       |     |
| ----- | ---- | ---- | ---- | ---- | ----- | --- |
| 7Z    | CSV  | GIF  | MIDI | PNG  | TIF   | ZIP |
| AVI   | DOC  | GZ   | MKV  | PPT  | TIFF  | ZST |
| AVIF  | DOCX | ICO  | MP3  | PPTX | TTF   |     |
| APK   | DMG  | ISO  | MP4  | PS   | WEBM  |     |
| BIN   | EJS  | JAR  | OGG  | RAR  | WEBP  |     |
| BMP   | EOT  | JPG  | OTF  | SVG  | WOFF  |     |
| BZ2   | EPS  | JPEG | PDF  | SVGZ | WOFF2 |     |
| CLASS | EXE  | JS   | PICT | SWF  | XLS   |     |
| CSS   | FLAC | MID  | PLS  | TAR  | XLSX  |     |

To cache additional content, refer to [Cache Rules](/cache/how-to/cache-rules/) to create a rule to cache everything.

<Render
  file="edge-ttl"
	product="cache"
	params={{
		conditional: "dedicated-cache-tile"
	}}
/>

## Customization options and limits

Cloudflare’s CDN provides several cache customization options:

* Caching behavior for individual URLs via [Cache Rules](/cache/how-to/cache-rules/)

 <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

* Customize caching with [Cloudflare Workers](/workers/reference/how-the-cache-works/)
* Adjust caching level, cache TTL, and more in the Caching page in the Cloudflare dashboard:

 <DashButton url="/?to=/:account/:zone/caching/configuration" />

### Upload limits

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |
| Max upload size | 100 MB | 100 MB | 200 MB | Up to 5 GB |

Customers can adjust the **Maximum Upload Size** from the zone's **Network** page. Enterprise customers can self-serve any value up to 5 GB. Uploads larger than 5 GB require additional configuration — contact your account team or [Cloudflare Support](/support/contacting-cloudflare-support/).

:::note
Very large uploads take longer to transfer. Requests that exceed the connection or read timeout can fail mid-upload before reaching the size limit — a failure that may look unrelated to **Maximum Upload Size**. If you raise the limit substantially, make sure your clients and origin allow enough time for the full transfer.
:::

If you require a larger upload, you can group requests into smaller chunks, upload the full resource through a [DNS-only (unproxied) DNS record](/dns/proxy-status/) or [upgrade your plan](/billing/manage/change-plan/).

### Cacheable size limits

Cloudflare cacheable file limits:

* Free, Pro and Business customers have a limit of 512 MB.
* For Enterprise customers the default maximum cacheable file size is 5 GB. Contact your account team or [Cloudflare Support](/support/contacting-cloudflare-support/) to request a limit increase.

## When does Cloudflare cache successfully?

The connection status between visitors and Cloudflare can vary, affecting whether Cloudflare caches the content or not. If Cloudflare has already established a connection to the origin and started fetching the content, it will continue to retrieve and cache the entire content, even if the visitor disconnects midway. However, if a visitor disconnects before the origin responds to Cloudflare's request, no content will have been fetched yet, so Cloudflare will not start caching the content.

# Concepts

import { DirectoryListing } from "~/components"

Review the following topics to learn more about the basic concepts and components of Cloudflare Cache.

<DirectoryListing />

# Retention vs Freshness (TTL)

In the context of Cloudflare CDN (Content Delivery Network), retention and freshness refer to two separate but related concepts. For an object in cache, freshness is how long it should be considered valid without consulting its source, while retention refers to how long it stays in cache before being removed.

## Retention

When a resource is requested, Cloudflare caches it so that subsequent requests can be served without contacting the origin server. If a cached object is not requested again, it is eventually removed to make room for newer, more popular content. This removal process is called eviction.

Cloudflare uses a Least Recently Used (LRU) algorithm to decide which objects to evict when the cache is full. An object's retention period is how long it stays in cache before being evicted. Retention is determined by the object's relative popularity and the size of the cache, and is not configurable.

## Freshness (TTL)

Freshness, also known as Time to Live (TTL), determines how long a cache can use an object without checking with the origin again. For example, if an object has a TTL of five minutes, the cache serves it directly for five minutes after first receiving it. After five minutes, Cloudflare must check with the origin to confirm the object is still valid before serving it again. There are a few ways to configure TTLs for resources served through Cloudflare's CDN:

* Include [Origin Cache Control](/cache/concepts/cache-control/) or [CDN Cache Control](/cache/concepts/cache-control/) directives, like `max-age` or `s-maxage`, in the origin cache-control response header.

* Use [Cache Rules](/cache/how-to/cache-rules/) or [Workers](/cache/interaction-cloudflare-products/workers/).

If an object in cache is no longer fresh, Cloudflare revalidates it with the origin. When [`stale-while-revalidate`](/cache/concepts/cache-control/#revalidation) is set, revalidation happens asynchronously at expiry — visitors continue to be served from cache while Cloudflare fetches a fresh copy in the background. Without this directive, incoming requests wait for the origin to respond before receiving content. The origin can either confirm the cached object is still valid (refreshing its TTL) or return a new version to replace it. Refer to [Revalidation](/cache/concepts/revalidation/) for details.

# Revalidation

Cloudflare revalidates cached content with your origin after the content expires. To revalidate cached content before it expires, [invalidate it](/cache/guides/invalidate-cache/).

## Stale-while-revalidate

When a cached asset expires, Cloudflare uses the [`stale-while-revalidate`](/cache/concepts/cache-control/#revalidation) directive in `Cache-Control` to determine whether it can continue serving the stale asset while fetching a fresh copy from the origin. If the directive is present and the asset is within the allowed staleness window, Cloudflare serves the expired content to visitors and revalidates in the background. By using headers like `If-Modified-Since` and `ETag`, Cloudflare validates content without fully re-fetching it, reducing origin traffic.

## Asynchronous revalidation

Revalidation is fully asynchronous. When a cached asset expires and `stale-while-revalidate` is set, the first request that arrives after expiry triggers revalidation in the background. That request immediately receives stale content with an [UPDATING](/cache/concepts/cache-responses/#updating) status instead of blocking until the origin responds. All following requests also receive stale content with an `UPDATING` status until the origin responds. Once revalidation completes, subsequent requests receive fresh content with a [HIT](/cache/concepts/cache-responses/#hit) status.

If the stale content is still valid, Cloudflare sets a new TTL. If the content has changed, the origin provides fresh content to replace the old.

## Controlling stale behavior

Cloudflare only serves stale content during revalidation if your origin includes the `stale-while-revalidate` directive in its `Cache-Control` header. Without this directive, visitors wait for the origin to respond before receiving content.

If your origin sets `stale-while-revalidate` but you want to override it, you can disable stale serving through the [Serve stale content while revalidating](/cache/how-to/cache-rules/settings/#serve-stale-content-while-revalidating) setting in Cache Rules.

### Directives that disable stale-while-revalidate

When [Origin Cache Control](/cache/concepts/cache-control/#enable-origin-cache-control) is enabled, the following `Cache-Control` directives prevent Cloudflare from serving stale content, per [RFC 9111 §4.2.4](https://www.rfc-editor.org/rfc/rfc9111.html#section-4.2.4):

- **`must-revalidate`** — Prohibits serving stale content; the cache must revalidate with the origin first.
- **`proxy-revalidate`** — Same as `must-revalidate`, but only applies to shared caches (like Cloudflare).
- **`s-maxage`** — Implies `proxy-revalidate` semantics, so shared caches cannot serve stale content.
- **`no-cache`** — Requires revalidation before serving any cached response.

If any of these directives are present alongside `stale-while-revalidate`, Cloudflare will not serve stale content — requests will return `EXPIRED` instead of `UPDATING`.

:::note[Workaround for different edge and browser TTLs]
If you need `stale-while-revalidate` behavior with a different TTL for Cloudflare than for browsers, do not use `s-maxage`. Instead, configure your origin to send `max-age` with `stale-while-revalidate`, then use [Edge Cache TTL](/cache/how-to/cache-rules/settings/#edge-ttl) in Cache Rules to set a separate TTL for Cloudflare.
:::

For all available directives or behavior when Origin Cache Control is disabled, refer to [Cache-Control directives](/cache/concepts/cache-control/#cache-control-directives).

## Smart revalidation towards users

When both [`Last-Modified`](https://datatracker.ietf.org/doc/html/rfc7232?cf_history_state=%7B%22guid%22%3A%22C255D9FF78CD46CDA4F76812EA68C350%22%2C%22historyId%22%3A15%2C%22targetId%22%3A%226C8153BAEF7BC0C5A331E28F8BCF1ABA%22%7D#section-2.2) and [`Etag`](https://datatracker.ietf.org/doc/html/rfc7232?cf_history_state=%7B%22guid%22%3A%22C255D9FF78CD46CDA4F76812EA68C350%22%2C%22historyId%22%3A13%2C%22targetId%22%3A%226C8153BAEF7BC0C5A331E28F8BCF1ABA%22%7D#section-2.3) headers are absent from the origin server response, Smart Edge Revalidation will use the time the object was cached on Cloudflare's global network as the `Last-Modified` header value. When a browser sends a revalidation request to Cloudflare using `If-Modified-Since` or `If-None-Match`, our global network can answer those revalidation questions using the `Last-Modified` header generated from Smart Edge Revalidation. In this way, our global network can ensure efficient revalidation even if the headers are not sent from the origin.

# Vary

The [`Vary`](https://www.rfc-editor.org/rfc/rfc9110.html#name-vary) HTTP response header tells Cloudflare that an origin can serve different responses for the same URL depending on request headers. For example, an origin might serve different languages based on `Accept-Language`, or different content formats based on `Accept`.

By default, Cloudflare's CDN constructs [cache keys](/cache/how-to/cache-keys/) from a request's URL and a handful of specific headers. [Cache Rules](/cache/how-to/cache-rules/) can add other request properties to the cache key ahead of time. The `Vary` response header lets the origin decide which request headers matter when Cloudflare receives the response.

:::note
A response containing `Vary: *` always bypasses cache, regardless of your Vary configuration.
:::

This page explains how Vary affects caching. To configure Vary, use [Vary](/cache/how-to/cache-rules/settings/#vary) in Cache Rules settings, or [`cf.vary`](/workers/runtime-apis/request/#the-cfvary-property) for Workers subrequests.

This feature is distinct from [Vary for images](/cache/advanced-configuration/vary-for-images/), which serves image format variants based on the `Accept` header through a separate cache variants rule.

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |

## How Vary affects cache keys

When Cloudflare caches a response with a `Vary` header, the listed request headers become part of the cache key for that response, following the HTTP caching behavior described in [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-cache-keys-with). The same URL can then have multiple cached versions, each selected by the request header values named in the origin's `Vary` response.

Cloudflare does not vary every cached response just because Vary is configured in a Cache Rule. The origin response must include a `Vary` header. Cloudflare then uses the configured action for each listed header to decide which request header value is added to the cache key.

For example, assume the origin returns this response:

```txt
Vary: Accept-Language
Cache-Control: public, max-age=3600
```

This tells Cloudflare that the value of the `Accept-Language` request header should be part of the cache key.

With `accept-language` configured to `normalize`, these two requests can use the same cached version:

```txt
Accept-Language: en-US, fr;q=0.8
Accept-Language: fr;q=0.8, en-GB
```

Both request headers normalize to the same language preference order, `en,fr`. A request with a different normalized value, such as `Accept-Language: fr, en;q=0.8`, creates or selects a different cached version of the same URL.

When a response varies on multiple headers, Cloudflare includes each listed header in the cache key. For example, a response with `Vary: Accept, Accept-Language` uses both the configured `accept` value and the configured `accept-language` value to select the cached response.

If the origin response does not include a `Vary` header, Cloudflare caches the response normally. If the origin response includes a `Vary` header name configured to `bypass` cache, Cloudflare does not store that response.

## Actions

Each configured header uses one of three actions:

| Action        | Meaning                                                                                                                                                       | When to use                                                                               |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `normalize`   | Normalize the request header value before selecting the cached version. For selected headers, Cloudflare may also forward the normalized value to the origin. | Most `Accept`, `Accept-Language`, and `Accept-Encoding` use cases.                        |
| `passthrough` | Use the raw request header value to select the cached version. The header is forwarded to the origin unchanged.                                               | When byte-for-byte differences in the header value should create different versions.      |
| `bypass`      | Bypass cache when this header name appears in the origin's `Vary` response.                                                                                   | Headers with too many possible values, per-user values, or values you do not want cached. |

### Normalize

`normalize` reduces unnecessary cached versions by converting equivalent request header values into the same cache key value.

For example, these two `Accept` headers can normalize to the same value:

```txt
Accept: text/html, application/json;q=0.9
Accept: application/json;q=0.9, text/html
```

### Passthrough

`passthrough` uses the raw request header value when selecting a cached version. Semantically equivalent values can still create different cached versions if the bytes differ.

For example, under `passthrough`, these two requests select different cached versions:

```txt
Accept: text/html, application/json
Accept: application/json, text/html
```

Use `passthrough` only when the exact header value matters to your origin and should matter to the cache.

### Bypass

`bypass` tells Cloudflare not to cache a response when the origin's `Vary` response includes that header name.

For example, if your configuration sets `user-agent` to `bypass`, a response with this header is not cached:

```txt
Vary: User-Agent
```

## Normalization behavior

Vary normalization is the normalization performed when the configured action is `normalize`. It affects how Cloudflare selects a cached version and, for some headers, what Cloudflare forwards to the origin.

Normalization is optional but recommended for most deployments because it reduces the number of cached versions and improves cache hit ratio.

When a header's action is `normalize`, Cloudflare uses the normalized value to select the cached version. Normalization can be lossy: it may reorder values, drop quality values, lowercase values, or remove entries that are not in a configured allowlist.

### Origin request headers

For `Accept`, `Accept-Language`, and `Accept-Encoding` with [Respect Strong ETags](/cache/how-to/cache-rules/settings/#respect-strong-etags) enabled, Cloudflare may also forward the normalized header value to the origin. This keeps the response generated by the origin consistent with the cache key value Cloudflare uses for caching.

For example, if `accept-language` normalizes these two requests to `en,fr`, Cloudflare forwards `Accept-Language: en,fr` to the origin on a cache miss or revalidation:

```txt
Accept-Language: en-US, fr;q=0.8
Accept-Language: fr;q=0.8, en-GB
```

Forwarding the normalized value prevents Cloudflare from storing a response generated for one raw header value under a broader normalized value that another request could later reuse incorrectly.

This origin request rewrite applies to:

- `Accept`
- `Accept-Language`
- `Accept-Encoding`, only when Respect Strong ETags is enabled

This rewrite does not apply to:

- Headers configured as `passthrough`
- Headers configured as `bypass`
- Other generic headers

This rewrite happens before Cloudflare receives the origin response, so it is based on your Cache Rule configuration. If `Accept`, `Accept-Language`, or `Accept-Encoding` is configured with `normalize`, Cloudflare rewrites that request header when forwarding to the origin even if the origin's eventual response does not list that header in `Vary`. Cache selection and bypass still depend on the origin response's `Vary` header.

If normalization reduces a header to an empty value — for example, because none of the request's values match a configured `media_types` or `languages` list — Cloudflare removes that header from the origin request.

### Accept

Cloudflare normalizes the `Accept` request header in the following steps:

1. Convert MIME types to lowercase.
2. Strip optional whitespace.
3. Sort MIME types by quality value. Types with the same quality value are sorted alphabetically.
4. Strip parameters.

Quality values are used for sorting and then removed from the normalized value. `q=0` is preserved because it means "not acceptable" and should remain distinguishable from a low-priority value.

You can provide an optional `media_types` list. If provided, any MIME type not in the list is removed from the normalized value.

### Accept-Language

Cloudflare normalizes the `Accept-Language` request header in the following steps:

1. Convert languages to lowercase.
2. Strip optional whitespace.
3. Sort languages by quality value. Languages with the same quality value are sorted alphabetically.
4. Strip parameters.
5. Strip region variants. For example, `en-US` becomes `en`. If multiple region variants of the same language are present, they are combined into a single item.

Quality values are used for sorting and then removed from the normalized value. `q=0` is preserved because it means "not acceptable" and should remain distinguishable from a low-priority value.

You can provide an optional `languages` list. If provided, any language not in the list is removed from the normalized value. If an item in the list specifies a region variant, and there is a matching item in the request header, the region variant is retained in the normalized value.

### Accept-Encoding

By default, Cloudflare's CDN overrides the `Accept-Encoding` header based on the enabled compression encodings. If Brotli compression is enabled, the `Accept-Encoding` forwarded to the origin is `gzip, br`. If Brotli compression is not enabled, the `Accept-Encoding` forwarded to the origin is `gzip`. Cloudflare can then recompress cached assets based on the visitor's `Accept-Encoding`. For details, refer to [ETag headers](/cache/reference/etag-headers/).

This behavior can be turned off by enabling [Respect Strong ETags](/cache/how-to/cache-rules/settings/#respect-strong-etags). With Respect Strong ETags enabled, the visitor's `Accept-Encoding` is forwarded to the origin instead of Cloudflare's compression override. If Vary normalization is enabled for `Accept-Encoding`, the normalized value is used both to select the cached version and as the value forwarded to the origin.

Because Cloudflare controls `Accept-Encoding` when Respect Strong ETags is turned off, `Accept-Encoding` normalization only rewrites the origin request when Respect Strong ETags is turned on.

Cloudflare normalizes the `Accept-Encoding` request header in the following steps:

1. Convert encodings to lowercase.
2. Strip optional whitespace.
3. Sort encodings by quality value. Encodings with the same quality value are sorted alphabetically.
4. Strip parameters.

Quality values are used for sorting and then removed from the normalized value. `q=0` is preserved because it means "not acceptable" and should remain distinguishable from a low-priority value.

### Other headers

For any header other than `Accept`, `Accept-Language`, and `Accept-Encoding`, Cloudflare does not know the field's semantics. Normalization is restricted to transformations that are safe for any header:

- Multiple header field lines for the same header are combined into a single comma-separated value in the order received.
- Optional whitespace around each value is trimmed.

Values are not reordered, lowercased, deduplicated, or otherwise altered, because the order and contents of an arbitrary header may be significant.

For example, these two header field lines:

```txt
X-Custom-Header: Value2
X-Custom-Header: Value1
```

When selecting a cached version, Cloudflare combines these values as `Value2,Value1`. The header forwarded to the origin is not rewritten.

## Purge behavior

Purging a URL purges all cached versions for that URL. You do not need to send a separate [purge](/cache/how-to/purge-cache/) request for each `Vary` header value. This applies to purge methods that target the cached object, such as purge by URL, tag, hostname, prefix, or purge everything.

Changing Vary configuration does not itself purge cached content. Because a new Vary configuration can change how cached versions are selected, requests may miss and refill under the new cache keys until the old cached entries expire or are purged.

# Guides

import { DirectoryListing } from "~/components";

Follow these guides to manage cached content and verify results.

<DirectoryListing />

# Invalidate cached content

import { DashButton, Steps } from "~/components";

Invalidation marks cached content as stale. On the next request, Cloudflare revalidates the content with your origin. If your origin responds with `304 Not Modified`, Cloudflare reuses the cached content instead of downloading it again.

Invalidation is sometimes called soft purge.

## Choose between purge and invalidation

Purge and invalidation accept the same selectors: URLs, cache tags, hostnames, URL prefixes, or everything. They differ in what happens to matching content:

| Behavior       | Purge                                      | Invalidate                                                 |
| -------------- | ------------------------------------------ | ---------------------------------------------------------- |
| Cached content | Removed                                    | Kept and marked as stale                                   |
| Next request   | Fetches the full response from your origin | Revalidates with your origin                               |
| Stale content  | Not served                                 | Can be served while revalidating or when your origin fails |
| API endpoint   | `purge_cache`                              | `invalidate_cache`                                         |

Use purge when cached content must not be served again. Update or remove the content at your origin before you purge it. Otherwise, the next request can cache the old version again.

Use invalidation to refresh content that might not have changed, such as a group of assets that share a cache tag.

## How invalidation works

Invalidation does not fetch new content in advance. Instead, the next request for invalidated content triggers revalidation.

To revalidate, Cloudflare sends your origin a conditional request. The request uses the `ETag` or `Last-Modified` header that your origin sent with the content. Your origin's response determines what happens next:

- If your origin responds with `304 Not Modified`, Cloudflare reuses the cached content and sets a new time to live (TTL).
- If your origin returns new cacheable content, Cloudflare replaces the cached content.
- If your origin returns a `5xx` error or cannot be reached, Cloudflare can serve the cached content stale. For details, refer to [Stale content during revalidation](#stale-content-during-revalidation).

Invalidation does not guarantee that content is reused. Cloudflare fetches the full response if the content is no longer cached, your origin sent neither header, or your origin does not support conditional requests.

A `304 Not Modified` response does not refresh cache tags set by [Cache Response Rules](/cache/how-to/cache-response-rules/). Cache tags from your origin's `Cache-Tag` header are updated only if your origin includes that header in the `304` response. To refresh cache tags otherwise, purge the content.

### Stale content during revalidation

Your cache settings and your origin's health determine whether visitors receive stale content while Cloudflare revalidates:

- With `stale-while-revalidate`, Cloudflare can serve stale content while it revalidates in the background.
- Without `stale-while-revalidate`, requests wait for revalidation to complete.
- If your origin returns a `5xx` error or cannot be reached, Cloudflare can serve stale content for as long as the content stays in cache. This happens even if the response has no `stale-if-error` directive. To limit this, set `stale-if-error` in the response. To prevent it, set `stale-if-error=0`. With [Origin Cache Control](/cache/concepts/cache-control/) enabled, `must-revalidate`, `proxy-revalidate`, and `s-maxage` also prevent it.

For content that is still fresh, `stale-while-revalidate` and `stale-if-error` windows start when you invalidate it, not when it would have expired. Invalidation never extends a stale window that has already ended.

For example, suppose you invalidate a cached response with the following headers before it expires:

```http
Cache-Control: public, max-age=3600, stale-while-revalidate=30
ETag: "product-v1"
```

Cloudflare can serve this response stale for up to 30 seconds after the invalidation while it revalidates. The response has no `stale-if-error` directive. If your origin returns a `5xx` error or cannot be reached, Cloudflare can serve the response stale beyond those 30 seconds. It can do so for as long as the response stays in cache.

Other directives and Cache Rules settings can prevent Cloudflare from serving stale content while it revalidates. For details, refer to [Revalidation](/cache/concepts/revalidation/).

### Cache Reserve

Invalidation keeps matching content in [Cache Reserve](/cache/advanced-configuration/cache-reserve/), where it continues to incur storage costs. If your origin responds with `304 Not Modified`, Cloudflare reuses the stored content instead of fetching it from your origin again. Content served from Cache Reserve is not served stale while it revalidates, even with `stale-while-revalidate`.

Compared with purging, invalidation reduces origin egress but not Cache Reserve operations. Updating the stored content after a `304` response is a Class A operation. Invalidating by URL also updates the stored content when you send the request, which is a Class A operation. For details, refer to [Cache Reserve operations](/cache/advanced-configuration/cache-reserve/#operations).

Purge forces a cache miss for matching Cache Reserve content. For details, refer to [Cache Reserve purge behavior](/cache/advanced-configuration/cache-reserve/#purge-behavior).

### Workers Cache

Invalidation from the dashboard or the `invalidate_cache` endpoint does not affect [Workers Cache](/workers/cache/). Workers Cache belongs to your Worker, not to your zone. To remove responses from a Worker's cache, call [`ctx.cache.purge()`](/workers/cache/purge/) from the Worker.

## Before you begin

Check the following requirements before you invalidate content:

- **Origin validators**: To reuse cached content, your origin must return an `ETag` or `Last-Modified` header and support conditional requests.
- **Permissions**: For API requests, use a token with the **Cache Purge** permission for the zone. To invalidate from the dashboard, your role must include the same permission.
- **Rate limits**: Invalidation requests count toward the purge limits for your account. For details, refer to [Limits](#limits).

## Invalidate using the dashboard

<Steps>

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. If your zone uses [Version Management](/version-management/), select the **Global** tab. Then, in **Invalidate Cache**, choose the environment under **Choose the environment**. The dashboard preselects an environment, so check the selection before you continue.
3. In **Invalidate Cache**, select **Custom Invalidation**. If your zone uses Version Management, this button is labeled **Select Content**.
4. Under **Select content by**, choose **URL**, **Hostname**, **Tag**, or **Prefix**, and enter the values to invalidate.
5. Select **Invalidate**.

</Steps>

To invalidate all cached content, select **Invalidate Everything**, and then confirm.

If your zone uses Version Management, **Invalidate Everything** is not available. Instead, choose **Everything: All cached content** under **Select content by**, and then select **Invalidate**. The dashboard does not ask you to confirm this option. Invalidating everything in the Production environment applies to all environments.

If your [cache key](/cache/how-to/cache-keys/) includes the visitor's device type or country, Enterprise zones can invalidate those variants of a URL from the dashboard. Select **URL**, and then set the device type or country under **Advanced (custom cache keys)**. For other cache key headers, use the API.

## Invalidate using the API

To invalidate content, send a `POST` request to the `invalidate_cache` endpoint:

```txt
https://api.cloudflare.com/client/v4/zones/{zone_id}/invalidate_cache
```

The request body uses the same format as the corresponding [purge request](/api/resources/cache/methods/purge/). The endpoint determines whether Cloudflare purges or invalidates the content.

In the following examples, replace `$ZONE_ID` with your [zone ID](/fundamentals/account/find-account-and-zone-ids/) and `$CLOUDFLARE_API_TOKEN` with your [API token](/fundamentals/api/get-started/create-token/).

### Invalidate by URL

To invalidate specific URLs, list them in `files`:

```bash
curl --request POST \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/invalidate_cache" \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "files": ["https://www.example.com/images/product.jpg"]
  }'
```

If your [cache key](/cache/how-to/cache-keys/) includes request headers, include the header values that identify the cached variant:

```json
{
	"files": [
		{
			"url": "https://www.example.com/images/product.jpg",
			"headers": {
				"CF-Device-Type": "desktop",
				"CF-IPCountry": "US"
			}
		}
	]
}
```

For URL matching requirements, refer to [Purge by single-file](/cache/how-to/purge-cache/purge-by-single-file/).

### Invalidate by tag, hostname, prefix, or everything

Use the request body that matches the content you want to invalidate:

| Selector     | Request body                              |
| ------------ | ----------------------------------------- |
| Cache tags   | `{"tags":["product-images"]}`             |
| Hostnames    | `{"hosts":["images.example.com"]}`        |
| URL prefixes | `{"prefixes":["www.example.com/images"]}` |
| Everything   | `{"purge_everything":true}`               |

Prefixes include a hostname and path, without a URL scheme.

For example, to invalidate all content tagged `product-images`, send this request:

```bash
curl --request POST \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/invalidate_cache" \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"tags":["product-images"]}'
```

Invalidating everything can trigger revalidation for a large amount of content as requests arrive. Select the smallest set of content that meets your needs.

### Invalidate content in an environment

For a non-production [Version Management](/version-management/) environment, include the environment ID in the path:

```bash
curl --request POST \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/environments/$ENVIRONMENT_ID/invalidate_cache" \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"tags":["product-images"]}'
```

To find the environment ID, refer to [Purge zone versions via API](/cache/how-to/purge-cache/purge-zone-versions/). For production, use the zone-level endpoint.

A request to invalidate everything in a non-production environment applies only to that environment. The same request to the zone-level endpoint applies to all environments.

## Limits

Invalidation requests count toward the same account-level [purge limits](/cache/how-to/purge-cache/#availability-and-limits) as purge requests. The limits that apply depend on how you select content:

| Selector                                           | Limits that apply                                                                                                                        |
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| URLs                                               | [Single-file purge limits](/cache/how-to/purge-cache/#single-file-purge-limits)                                                          |
| Cache tags, hostnames, URL prefixes, or everything | [Hostname, tag, prefix URL, and purge everything limits](/cache/how-to/purge-cache/#hostname-tag-prefix-url-and-purge-everything-limits) |

Purge and invalidation requests draw from the same limits. Heavy use of one reduces the capacity available for the other. Like purge limits, these limits apply per account and are shared by zones on the same plan.

Each invalidation request can include the same maximum number of items as a purge request.

## Verify an invalidation

To verify an invalidation, use a cacheable test URL whose origin returns an `ETag` or `Last-Modified` header. Keep the content unchanged at your origin during the test. To observe revalidation directly, omit `stale-while-revalidate` from the test response and set a positive TTL.

<Steps>

1. Request the test URL until the `CF-Cache-Status` header returns `HIT`.
2. Invalidate the URL.
3. Request the URL again and check its response headers:

   ```bash
   curl --silent --show-error --dump-header - --output /dev/null \
     "https://www.example.com/images/product.jpg"
   ```

4. In your origin logs, confirm that Cloudflare sent a conditional request and that your origin returned `304`.

</Steps>

For cached content, expect the following results:

| Result                                                 | `CF-Cache-Status`         |
| ------------------------------------------------------ | ------------------------- |
| Your origin confirms that the content has not changed  | `REVALIDATED`, then `HIT` |
| Your origin returns new content                        | `EXPIRED`, then `HIT`     |
| Your origin sends no `ETag` or `Last-Modified` header  | `EXPIRED`, then `HIT`     |
| Cloudflare serves stale content while revalidating     | `UPDATING`, then `HIT`    |
| Your origin returns a `5xx` error or cannot be reached | `STALE`                   |
| The content is no longer cached                        | `MISS`                    |

Without an `ETag` or `Last-Modified` header from your origin, Cloudflare has nothing to revalidate with, so it fetches the full response. When your origin returns `304`, Cloudflare can still return `200 OK` to the visitor with the cached content.

With [Tiered Cache](/cache/how-to/tiered-cache/), a lower-tier data center revalidates with its upper-tier data center. A visitor can see `EXPIRED` or `MISS` even when your origin returned `304`. Concurrent requests can also affect which status you observe. Use your origin logs to confirm revalidation. For status definitions, refer to [Cloudflare cache responses](/cache/concepts/cache-responses/).

## Troubleshoot invalidation

### Your origin returns full responses

Confirm that the content was cached and that your selectors match it. Also confirm that your origin returns an `ETag` or `Last-Modified` header and responds to conditional requests with `304 Not Modified`.

Check these headers in a request sent directly to your origin, not through Cloudflare. When your origin sends neither header, responses from Cloudflare can still include a `Last-Modified` header because of [smart revalidation towards users](/cache/concepts/revalidation/#smart-revalidation-towards-users). Cloudflare does not use that header when it revalidates with your origin.

If the content was also purged after it was cached, the purge takes precedence, and the next request fetches the full response.

### Visitors receive stale content

While Cloudflare revalidates, it serves stale content only if the cached response includes `stale-while-revalidate`. You can turn this off with the [Serve stale content while revalidating](/cache/how-to/cache-rules/settings/#serve-stale-content-while-revalidating) setting in Cache Rules.

If your origin returns a `5xx` error or cannot be reached, Cloudflare serves invalidated content stale by default, for as long as it stays in cache. The Cache Rules setting does not change this. To limit how long Cloudflare serves stale content when your origin fails, set `stale-if-error` in your origin responses. To prevent it, set `stale-if-error=0`. With [Origin Cache Control](/cache/concepts/cache-control/) enabled, `must-revalidate`, `proxy-revalidate`, and `s-maxage` also prevent it.

To stop serving cached content, [purge](/cache/how-to/purge-cache/) it instead.

# Always Online

import { DashButton } from "~/components"

Cloudflare’s Always Online feature is now integrated with the [Internet Archive](https://archive.org/) so that visitors can access a portion of your website even when your origin server is unreachable and a Cloudflare-cached version is unavailable. When your origin is unreachable, Always Online checks Cloudflare’s cache for a stale or expired version of your website. If a version does not exist, Cloudflare goes to the Internet Archive to fetch and serve static portions of your website.

When you enable Always Online with Internet Archive integration, Cloudflare shares your hostname and popular URL paths with the archive so that the Internet Archive’s crawler stores the pages you want archived. When submitting targets to the crawler, Cloudflare identifies the most popular URLs found among GET requests that returned a 200 HTTP status code in the previous five hours.

Note that Cloudflare does not save a copy of every page of your website, and it cannot serve dynamic content while your origin is offline. If the requested page is not in the Internet Archive's Wayback Machine, the visitor sees the actual error page caused by the offline origin web server.

When the Internet Archive integration is enabled, Cloudflare tells the Internet Archive what pages to crawl and how often. The pages to crawl, as previously mentioned, are the most popular URLs that were successfully visited in the last five hours. The crawling intervals, to ensure stability of service, are limited by Cloudflare. Limits vary according to your Cloudflare plan.

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |
| Crawl interval | Every 30 days | Every 15 days | Every 5 days | Every 5 days |

## Visitor Experience

When Always Online with Internet Archive integration is enabled, visitors see a banner at the top of the webpage explaining they are visiting an archived version of the website. Visitors can select the Refresh button to check whether the origin has recovered and fresh content is available.

When a visitor requests content and Cloudflare is unable to reach your origin server, Cloudflare returns an HTTP response status code in the range [520–527](/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-520/), depending on the issue. These status codes are generated by Cloudflare and indicate that the origin is unreachable. Always Online only activates when Cloudflare cannot connect to your origin — it does not activate when the origin is reachable but returning error responses.

If your origin is reachable and returns a [5XX](/support/troubleshooting/general-troubleshooting/troubleshooting-crawl-errors/#http-5xx-errors) status code (such as a 520), Always Online will not trigger because the origin is online. Always Online is designed to handle origin unreachability, not origin errors.

When the Internet Archive integration is enabled, Cloudflare checks the archive and serves the most recently archived version of the page.

Visitors who interact with dynamic parts of a website, such as a shopping cart or comment box, will see an error page caused by the offline origin web server.

## Enable Always Online

Here is how to enable Always Online in the dashboard:

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Choose the domain that will use Always Online with Internet Archive integration.
3. Under **Always Online**, set the toggle to **On**.

:::note[Note]


When turning on Always Online, you are also enabling the Internet Archive integration.


:::

Refer to [Always Online](/cache/troubleshooting/always-online/) for best practices, limitations, and FAQs.

# Cache keys

import { DashButton } from "~/components"

A Cache Key is an identifier that Cloudflare uses for a file in our cache, and the Cache Key Template defines the identifier for a given HTTP request.

A default cache key includes:

1. Full URL:
   * scheme - could be HTTP or HTTPS.
   * host - for example, `www.cloudflare.com`
   * URI with query string - for example, `/logo.jpg?utm_source=newsletter`
2. Origin header sent by client (for CORS support).
3. `x-http-method-override`, `x-http-method`, and `x-method-override` headers.
4. `x-forwarded-host`, `x-host`, `x-forwarded-scheme` (unless http or https), `x-original-url`, `x-rewrite-url`, and `forwarded` headers.

## Create custom cache keys

Custom cache keys let you precisely set the cacheability setting for any resource. They provide the benefit of more control, though they may reduce your cache hit rate and result in cache sharding:

1. In the Cloudflare dashboard, go to the **Cache Rules** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

2. Select **Create rule**.
3. Under **When incoming requests match**, define the [rule expression](/ruleset-engine/rules-language/expressions/edit-expressions/#expression-builder).
4. Under **Then**, in the **Cache eligibility** section, select **Eligible for cache**.
5. Add the **Cache Key** setting to the rule and select the appropriate **Query String** setting.
6. You can also select settings for **Headers**, **Cookie**, **Host**, and **User**.
7. To save and deploy your rule, select **Deploy**. If you are not ready to deploy your rule, select **Save as Draft**.

:::note
When [URL normalization](/rules/normalization/) is enabled, we recommend also enabling [Normalize URLs to origin](/rules/normalization/manage/), especially if you are setting custom cache keys or using [cache by device type](/automatic-platform-optimization/reference/cache-device-type/), which also modifies the cache key. This helps ensure the URL in the cache key matches the URL sent to the origin, preventing cache poisoning and ensuring consistent behavior.
:::

## Cache Key Template

There are a couple of common reasons to change the Cache Key Template. You might change the Cache Key Template to:

* Fragment the cache so one URL is stored in multiple files. For example, to store different files based on a specific query string in the URL.
* Consolidate the cache so different HTTP requests are stored in the same file. For example, to remove the Origin header added to Cloudflare Cache Keys by default.

### Impact of SSL settings on Cache behavior

Cloudflare's `$scheme` variable plays a key role in caching behavior, but its meaning varies depending on the cache key type:

- **Default Cache Key**: `$scheme` refers to the **origin scheme** — the protocol Cloudflare uses to connect to your origin server (HTTP or HTTPS). In this configuration, changes to your SSL settings (for example, switching from Flexible to Full) alter the origin scheme. Because the cache key includes the origin scheme, such changes trigger a cache bust, requiring Cloudflare to fetch content again from the origin.

- **Custom Cache Key**: `$scheme` refers to the **visitor's scheme** — the protocol used by the client making the request to Cloudflare. In this case, SSL setting changes do not impact the cache key unless the origin scheme is explicitly included in your custom configuration.

For example, with Flexible SSL, Cloudflare always connects to the origin over HTTP, regardless of whether the visitor uses HTTP or HTTPS. This results in the same cache key for both protocols under the default configuration.

Be aware that changes in the SSL setting can lead to cache invalidation when using the default cache key:

- Switching from **Off** to **Full**, **Full (strict)**, or **Strict** updates the origin scheme from HTTP to HTTPS, resulting in a cache bust.

- Moving from **Flexible** to **Full**, **Full (strict)**, or **Strict** similarly changes the origin scheme to HTTPS and causes a cache bust.

Understanding how `$scheme` interacts with your caching configuration is essential when modifying SSL modes to avoid unexpected cache behavior.

### Cache Level: Ignore Query String

A [Cache Level](/cache/how-to/set-caching-levels/) of Ignore Query String creates a Cache Key that includes all the elements in the default cache key, except for the query string in the URI that is no longer included. For instance, a request for `http://example.com/file.jpg?something=123` and a request for `http://example.com/file.jpg?something=789` will have the same cache key, in this case.

## Cache Key Settings

The following fields control the Cache Key Template.

### Query String

The query string controls which URL query string parameters go into the Cache Key. You can `include` specific query string parameters or `exclude` them using the respective fields. When you include a query string parameter, the `value` of the query string parameter is used in the Cache Key.

#### Example

If you include the query string foo in a URL like `https://www.example.com/?foo=bar`, then bar appears in the Cache Key. Exactly one of `include` or `exclude` is expected.

#### Usage notes

* To include all query string parameters (the default behavior), use include: `"\*"`
* To ignore query strings, use exclude: `"\*"`
* To include most query string parameters but exclude a few, use the exclude field which assumes the other query string parameters are included.

### Headers

Headers control which headers go into the Cache Key. Similar to Query String, you can include specific headers or exclude default headers.

When you include a header, the header value is included in the Cache Key. For example, if an HTTP request contains an HTTP header like `X-Auth-API-key: 12345`, and you include the `X-Auth-API-Key header` in your Cache Key Template, then `12345` appears in the Cache Key.

In the **Include headers and selected values** section, you can add header names and their values to the cache key. For custom headers, values are optional, but for the following restricted headers, you must include one to 10 specific values:

  * `accept`
  * `accept-charset`
  * `accept-encoding`
  * `accept-datetime`
  * `accept-language`
  * `referer`
  * `user-agent`

:::caution
For custom and restricted headers, selected values use case-sensitive substring matching. For example, `text/plain` matches an `Accept` header value of `text/plain, foo/bar`. It can also match the unintended value `text/plaintext`, but not `Text/Plain`.
:::

To check for the presence of a header without including its actual value, use the **Check presence of** option.

Currently, you can only exclude the `Origin` header. The `Origin` header is always included unless explicitly excluded. Including the [Origin header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin) in the Cache Key is important to enforce [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS).

Additionally, you cannot include the following headers:

* Headers that re-implement cache or proxy features
  * `connection`
  * `content-length`
  * `cache-control`
  * `if-match`
  * `if-modified-since`
  * `if-none-match`
  * `if-unmodified-since`
  * `range`
  * `upgrade`
* Headers that are covered by other Cache Key features
  * `cookie`
  * `host`
* Headers that are specific to Cloudflare and prefixed with `cf-`, for example, `cf-ray`
* Headers that are already included in the custom Cache Key template, for example, `origin`

### Host

Host determines which host header to include in the Cache Key.

* If `Use original host` (`resolved: false` in the API), Cloudflare includes the `Host` header in the HTTP request sent to the origin.
* If `Resolved host` (`resolved: true` in the API), Cloudflare includes the `Host` header that was resolved to get the `origin IP` for the request. The `Host` header may be different from the header actually sent if it has been changed with an [Origin Rule](/rules/origin-rules/features/#dns-record).

### Cookie

Like `query_string` or `header`, `cookie` controls which cookies appear in the Cache Key. You can either include the cookie value or check for the presence of a particular cookie.

#### Usage notes

You cannot include cookies specific to Cloudflare. Cloudflare cookies are prefixed with `__cf`, for example, `__cflb`

### User features

User feature fields add features about the end-user (client) into the Cache Key.

* `device_type` classifies a request as `mobile`, `desktop`, or `tablet` based on the User Agent
* `geo` includes the client’s country, derived from the IP address
* `lang` includes the first language code contained in the `Accept-Language` header sent by the client

:::note
APO's **Cache by device type** setting and the **Cache by device type** option in Cache Rules use the same mechanism to add device type to the cache key. Cache Rules lets you configure this manually without APO. Refer to [cache by device type in APO](/automatic-platform-optimization/reference/cache-device-type/) for details.
:::

## Availability

Cache keys options availability varies according to your plan.

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Cache deception armor | Yes | Yes | Yes | Yes |
| Cache by device type | Yes | Yes | Yes | Yes |
| Ignore query string | Yes | Yes | Yes | Yes |
| Sort query string | Yes | Yes | Yes | Yes |
| Query string | No | No | No | Yes |
| Headers | No | No | No | Yes |
| Cookie | No | No | No | Yes |
| Host | No | No | No | Yes |
| User features | No | No | No | Yes |

## Troubleshooting

You can use [Rules simulator](/rules/simulator/) to find which Cache Key settings were applied to your request. When you send a request through Rules simulator, if the request was served from cache, it will show a cache hit in the **Cache Parameters** section. Then select **View parameter detail** to see exactly which Cache Key properties were used.

## Limitations

The [Prefetch](/speed/optimization/content/prefetch-urls/) feature is not compatible with the [Custom Cache Keys](/cache/how-to/cache-rules/examples/custom-cache-key/). With [Cache Rules](/cache/how-to/cache-rules/), the custom cache key is used to cache all assets. However, Prefetch always uses the default cache key. This results in a key mismatch.

# Cache by status code

import { Render } from "~/components"

Customers can set cache time-to-live (TTL) based on the response status from the origin web server. Cache TTL refers to the duration of a resource in the Cloudflare network before being marked as `STALE` or discarded from cache. Status codes are returned by a resource's origin.

Setting cache TTL based on response status overrides the [default cache behavior (standard caching)](/cache/concepts/default-cache-behavior/) for static files and overrides cache instructions sent by the origin web server. To cache non-static assets, set a [Cache Level of Cache Everything using a Cache Rule](/cache/how-to/cache-rules/create-api/#example-requests). Setting `no-store` **Cache-Control** or a low TTL (using `max-age`/`s-maxage`) increases requests to origin web servers and decreases performance.

## Caching limits

The maximum caching limit for Free, Pro, and Business customers is 512 MB per file, and the maximum caching limit for Enterprise customers is 5 GB per file. If you need to raise the limits, contact your account team.

<Render
  file="edge-ttl"
	product="cache"
	params={{
		conditional: "dedicated-cache-tile"
	}}
/>

## Set cache TTL by response status via the Cloudflare dashboard

To set cache TTL by response status, [create a Cache Rule](/cache/how-to/cache-rules/) for [**Cache TTL by status code**](/cache/how-to/cache-rules/settings/#edge-ttl).

## Set cache TTL by response status via the Cloudflare API

```bash title="Request"
curl --request PUT \
"https://api.cloudflare.com/client/v4/zones/{zone_id}/rulesets/{ruleset_id}" \
--header "Authorization: Bearer <API_TOKEN>" \
--header "Content-Type: application/json" \
--data '{
  "rules": [
    {
      "expression": "(http.host eq \"www.example.com\")",
      "description": "set cache TTL by response status",
      "action": "set_cache_settings",
      "action_parameters": {
        "cache": true,
        "edge_ttl": {
          "status_code_ttl": [
            {
              "status_code_range": {
                "to": 299
              },
              "value": 86400
            },
            {
              "status_code_range": {
                "from": 300,
                "to": 499
              },
              "value": 0  // no-cache
            },
            {
              "status_code_range": {
                "from": 500
              },
              "value": -1  // no-store
            }
          ],
          "mode": "respect_origin"
        }
      }
    }
  ]
}'
```

### Syntax

Provide a JSON object containing status codes and their corresponding TTLs. Each key-value pair in the cache TTL by status cache rule has the following syntax:

* `status_code`: An integer value such as 200 or 500. `status_code` matches the exact status code from the origin web server. Valid status codes are between 100-999.
* `status_code_range`: Integer values for `from` and `to`. `status_code_range` matches any status code from the origin web server within the specified range.
* `value`: An integer value that defines the duration an asset is valid in seconds or one of the following strings: `no-store` (equivalent to `-1`), `no-cache` (equivalent to `0`).

## Set cache TTL by response status via a Cloudflare Worker

The **cacheTtlByStatus** option is a version of the **cacheTtl** feature that designates a cache TTL for a request’s response status code (for example, `{ "200-299": 86400, 404: 1, "500-599": 0 }`).

## TTL handling for 304 and 200 status codes

1. If a TTL is not explicitly set for status code `304`, we automatically set it to match the TTL of status code `200` (if the user has defined one for `200`).

2. If a user explicitly sets a different TTL for `304` than for `200`, the following behavior will occur:

- When a `200` response is received, the asset is cached with the TTL specified for status `200`.
- Once the asset expires and we revalidate with the origin, if the origin returns a `304`, the cache TTL is updated to the value set for `304`.

For example, if a user specifies a TTL of one hour for status `200` and 0 seconds (cache and always revalidate) for status `304`, the asset will be cached for 1 hour. After it expires, we revalidate with the origin. If the origin returns a `304`, each subsequent request will trigger revalidation. If the origin continues to return `304`, this cycle will persist.

This behavior is likely undesirable unless the user has a specific use case. Therefore, users should ensure that the TTL for `304` matches the TTL for `200` unless they intentionally require this behavior.

# Cache configuration

import { DirectoryListing } from "~/components"

Learn how to complete different configuration options to customize your cache behavior.

<DirectoryListing />

# Caching levels

Caching levels determine how much of your website’s static content Cloudflare should cache. Cloudflare’s CDN caches static content according to the levels below.

* **No Query String**: Delivers resources from cache when there is no query string. Example URL: `example.com/pic.jpg`
* **Ignore Query String**: Delivers the same resource to everyone independent of the query string. Example URL: `example.com/pic.jpg?ignore=this-query-string`
* **Standard (Default)**: Delivers a different resource each time the query string changes. Example URL: `example.com/pic.jpg?with=query`

You can adjust the caching level from the dashboard under **Caching** > **Configuration** > **Caching level**.

:::note[Note]


Ignore Query String only disregards the query string for static file extensions. For example, Cloudflare serves the `style.css` resource to requests for either `style.css?this` or `style.css?that`.


:::

## API Caching level values

If you are using the API to change the cache level, the values will differ from those shown in the dashboard. Refer to the table below to see how the API values map to the values shown in the dashboard.

| Dashboard           | API        |
| ------------------- | ---------- |
| No Query String     | Basic      |
| Ignore Query String | Simplified |
| Standard (Default)  | Aggressive |

# Tiered Cache

import { Details, APIRequest, Render, DashButton } from "~/components";

Tiered Cache uses the size of the Cloudflare network to reduce requests to customer origins by dramatically increasing cache hit ratios. With data centers around the world, Cloudflare caches content very close to end users. However, if a piece of content is not in cache, the Cloudflare edge data centers must contact the origin server to receive the cacheable content.

Tiered Cache works by dividing Cloudflare's data centers into a hierarchy of lower-tiers and upper-tiers. If content is not cached in lower-tier data centers (generally the ones closest to a visitor), the lower-tier must ask an upper-tier to see if it has the content. If the upper-tier does not have the content, only the upper-tier can ask the origin for content. This practice improves bandwidth efficiency by limiting the number of data centers that can ask the origin for content, which reduces origin load and makes websites more cost-effective to operate.

Additionally, Tiered Cache concentrates connections to origin servers so they come from a small number of data centers rather than the full set of network locations. This results in fewer open connections using server resources.

To enable Tiered Cache, refer to [Enable Tiered Cache](/cache/how-to/tiered-cache/#enable-tiered-cache).

## Tiered Cache Topology

Cloudflare allows you to select your cache topology so that you have control over how your origin connects to Cloudflare's data centers. This helps ensure higher cache hit ratios, fewer origin connections, and a reduction in Internet latency.

### Smart Tiered Cache

<Render file="smart-shield-callout" product="smart-shield" />

<Render file="smart-tiered-cache" product="cache" />

### Generic Global Tiered Cache

Generic Global Tiered Cache organizes Cloudflare's network into regional upper-tier pools. Cloudflare designs these pools to balance geographic scope, proximity to visitor-serving locations, and the distribution of suitably provisioned cache capacity. Each pool contains a set of eligible data centers that can serve as upper tiers.

For requests that use Tiered Cache, Generic Global uses a consistent hash of the cache key to select an upper-tier data center from the relevant regional pool. This normally directs requests with the same cache key in a region to the same upper-tier cache, reducing duplicate cache fills and improving cache efficiency.

Cloudflare updates the candidate pool as data-center availability, capacity, and network topology evolve. When the candidate pool changes, some cache keys may be reassigned.

Unlike Smart Tiered Cache, Generic Global does not use latency measurements between a particular origin and candidate upper-tier data centers to make this selection. Instead, it emphasizes regional locality and shared-cache efficiency. Smart Tiered Cache can use origin-specific performance and latency data to select upper tiers when that data is available.

To confirm whether a request used Tiered Cache, refer to the `CacheTieredFill` field in your [http_requests](/logs/logpush/logpush-job/datasets/zone/http_requests/#cachetieredfill) logs.

### Regional Tiered Cache

<Render file="smart-shield-callout" product="smart-shield" />

<Render file="regional-tiered-cache" product="cache" />

This can help improve performance for **Smart** and **Custom Tiered Cache** topologies with upper-tiers in one or two regions. Regional Tiered Cache is not beneficial for customers with many upper tiers in many regions like Generic Global Tiered Cache.

### Custom Tiered Cache

Custom Tiered cache allows Enterprise customers to work with their account team to set a custom topology that fits your specific needs, for instance you have close upper tiers or you have a unique traffic pattern. If you want a custom topology, please engage your account team.

## Tiered Cache and cache eligibility

Tiered Cache applies only to requests eligible for cache at request time. Origin-response cacheability is evaluated later, so an eligible request can be routed through an upper tier before Cloudflare treats the origin response as uncacheable. For details and request-time bypass options, refer to [Investigate uncached responses](/cache/troubleshooting/investigating-uncached-responses/).

For intermittent slowness that only affects requests routed through an upper tier, refer to [Investigate latency on tiered requests](/cache/troubleshooting/investigating-tiered-cache-latency/).

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Tiered Cache | Yes | Yes | Yes | Yes |
| Smart Topology | Yes | Yes | Yes | Yes |
| Generic Global Topology | No | No | No | Yes |
| Regional Tiered Cache | No | No | No | Yes |
| Custom Topology | No | No | No | Yes |

## Bandwidth Alliance

Enterprise customers can override Bandwidth Alliance configuration with Tiered Cache. For all other users, the Bandwidth Alliance takes precedence. Tiered Cache is still a valuable option to enable because the Bandwidth Alliance may not always be an available option, and in those instances, the Tiered Cache configuration will be used.

## Enable Tiered Cache

You can enable Tiered Cache in the dashboard or via API.

### Enable Tiered Cache in the dashboard

1. In the Cloudflare dashboard, go to the **Tiered Cache** page.

   <DashButton url="/?to=/:account/:zone/caching/tiered-cache" />

2. From **Tiered Cache**, toggle the button to **enabled**.
3. In **Tiered Cache Topology**, you can control how your origin connects to Cloudflare's data centers. You can select:
   * **Upper Tier Cache** - You have the option to choose between Smart or Generic Global Tiered Cache Topology.
   * **Middle Tier Cache** -  If you have selected Smart or Custom Tiered Cache Topology, you can now enable Regional Tiered Cache.
   * **Custom Tiered Cache** - Allows you to work with Cloudflare's support team to set a custom topology that fits your specific needs.
   * **Disable Tiered Cache**.

![Tiered Cache Topology dashboard](~/assets/images/cache/tiered_cache_topology.png)

### Enable Tiered Cache via API

To enable Tiered Cache via API use the following cURL example:

<APIRequest
  path="/zones/{zone_id}/argo/tiered_caching"
  method="PATCH"
  json={{
    value: "on"
  }}
/>

You can also configure Tiered Cache Topology via API, for instance:


<Details header="Enable Smart Tiered Cache">

<APIRequest
  path="/zones/{zone_id}/cache/tiered_cache_smart_topology_enable"
  method="PATCH"
  json={{
    value: "on"
  }}
/>


</Details>


<Details header="Enable Regional Tiered Cache">

<APIRequest
  path="/zones/{zone_id}/cache/regional_tiered_cache"
  method="PATCH"
  json={{
    value: "on"
  }}
/>

</Details>

For more API examples and configuration options for Tiered Cache, refer to the [API documentation](/api/resources/argo/subresources/tiered_caching/methods/get/).

### Set a cloud region hint

If your origin is hosted on a public cloud provider, set a cloud region hint so Smart Tiered Cache can select the optimal upper tier for your cloud region. For background on why this is needed, refer to [Public cloud origins](/cache/how-to/tiered-cache/#smart-tiered-cache).

Cloud region hints are available on all plan types (Free, Pro, Business, and Enterprise) at no additional cost. Supported providers: AWS, GCP, Azure, and Oracle Cloud.

#### Set a cloud region hint in the dashboard

1. Go to **Caching** > **Tiered Cache** > **Origin Configuration**.

   <DashButton url="/?to=/:account/:zone/caching/tiered-cache" />

2. Find your origin IP or hostname and select **Set Region Hint**.
3. Select your cloud provider and region (for example, `aws:us-east-1` or `gcp:europe-west1`).

#### List supported cloud regions via API

To see all available cloud providers and regions, use the supported regions endpoint:

<APIRequest
  path="/zones/{zone_id}/cache/origin_cloud_regions/supported_regions"
  method="GET"
/>

#### Set a cloud region hint via API

<APIRequest
  path="/zones/{zone_id}/cache/origin_cloud_regions"
  method="PATCH"
  json={{
    ip: "203.0.113.1",
    vendor: "aws",
    region: "us-east-1"
  }}
/>

:::note

Custom Tiered Cache settings take precedence over cloud region hint mappings. If you have a custom topology configured, the cloud region hint will not override it.

:::

:::note


To confirm that Tiered Cache is working, make sure you have the value of [CacheTieredFill](/logs/logpush/logpush-job/datasets/zone/http_requests/#cachetieredfill) in your http\_requests logs, this will indicate if Tiered Cache was used to serve the request.


:::

# Create a rule via API

import { Details, APIRequest } from "~/components";

Use the [Rulesets API](/ruleset-engine/rulesets-api/) to create a Cache Response Rule via API. To configure the Cloudflare API, refer to the [API documentation](/fundamentals/api/get-started/).

## Basic rule settings

When creating a Cache Response Rule via API, make sure you:

- Set the rule action to one of the [available actions](/cache/how-to/cache-response-rules/settings/#available-actions).
- Define the parameters in the `action_parameters` field according to the [settings](/cache/how-to/cache-response-rules/settings/) you wish to configure for matching responses.
- Deploy the rule to the `http_response_cache_settings` phase entry point ruleset.

## Procedure

1. Use the [List zone rulesets](/api/resources/rulesets/methods/list/) method to check if a ruleset already exists for the `http_response_cache_settings` phase.
2. If the phase ruleset does not exist, create it using the [Create a zone ruleset](/api/resources/rulesets/methods/create/) operation. In the new ruleset properties, set the following values:
   - kind: `zone`
   - phase: `http_response_cache_settings`
3. Use the [Update a zone ruleset](/api/resources/rulesets/methods/update/) operation to add rules to the ruleset. Alternatively, include the rules in the [Create a zone ruleset](/api/resources/rulesets/methods/create/) request mentioned in the previous step.

## Example requests

These examples demonstrate all the available actions in Cache Response Rules using request and response matching criteria. Using these examples directly will cause any existing rules in the phase to be replaced.

<Details header="Example: Strip response headers from JS files before caching">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_response_cache_settings" }}
  json={{
    rules: [
      {
        expression: 'http.request.uri.path.extension eq "js"',
        description: "Strip caching headers from JS files",
        action: "set_cache_settings",
        action_parameters: {
          strip_etags: true,
          strip_set_cookie: true,
          strip_last_modified: true
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details header="Example: Set static cache tags on API responses">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_response_cache_settings" }}
  json={{
    rules: [
      {
        expression: 'http.request.uri.path starts_with "/api/"',
        description: "Tag API responses for targeted purging",
        action: "set_cache_tags",
        action_parameters: {
          operation: "set",
          values: ["api-response", "dynamic-content"]
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details header="Example: Add cache tags from a response header using an expression">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_response_cache_settings" }}
  json={{
    rules: [
      {
        expression: 'any(http.response.headers.names[*] == "Surrogate-Keys")',
        description: "Extract cache tags from alternative CDN response header",
        action: "set_cache_tags",
        action_parameters: {
          operation: "add",
          expression: 'split(http.response.headers["Surrogate-Keys"][0], ",", 1)'
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details header="Example: Override cache-control with max-age ">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_response_cache_settings" }}
  json={{
    rules: [
      {
        expression: "http.response.code eq 200",
        description: "Override cache-control for successful responses",
        action: "set_cache_control",
        action_parameters: {
          "max-age": {
            operation: "set",
            value: 3600,
            cloudflare_only: true
          },
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details header="Example: Set private directive with qualifiers">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_response_cache_settings" }}
  json={{
    rules: [
      {
        expression: 'http.request.uri.path starts_with "/user/"',
        description: "Mark user content as private",
        action: "set_cache_control",
        action_parameters: {
          "private": {
            operation: "set",
            qualifiers: ["X-User-Id", "X-Session-Token"]
          },
          "no-cache": {
            operation: "set"
          }
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details header="Example: Set immutable for static font assets">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_response_cache_settings" }}
  json={{
    rules: [
      {
        expression: 'http.request.uri.path.extension in {"woff2" "woff" "ttf"}',
        description: "Mark fonts as immutable",
        action: "set_cache_control",
        action_parameters: {
          "immutable": {
            operation: "set"
          },
          "max-age": {
            operation: "set",
            value: 31536000
          }
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details header="Example: Multiple rules with strip headers, tag responses, and set cache control">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_response_cache_settings" }}
  json={{
    rules: [
      {
        expression: 'http.response.code eq 200 and http.request.uri.path.extension eq "html"',
        description: "Strip tracking headers from HTML responses",
        action: "set_cache_settings",
        action_parameters: {
          strip_etags: true,
          strip_set_cookie: true
        }
      },
      {
        expression: 'http.request.uri.path starts_with "/products/"',
        description: "Tag product pages for purging",
        action: "set_cache_tags",
        action_parameters: {
          operation: "add",
          values: ["product-catalog", "storefront"]
        }
      },
      {
        expression: "http.response.code eq 200",
        description: "Set cache control for all 200 responses",
        action: "set_cache_control",
        action_parameters: {
          "s-maxage": {
            operation: "set",
            value: 86400,
            cloudflare_only: true
          },
          "must-revalidate": {
            operation: "set"
          }
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

## Required API token permissions

The API token used in API requests to manage Cache Response Rules must have the following permissions:

- *Zone* > *Cache Rules* > *Edit*
- *Account Rulesets* > *Edit*
- *Account Filter Lists* > *Edit*

# Create a rule in the dashboard

import { Render, DashButton } from "~/components";

1. In the Cloudflare dashboard, go to **Cache** > **Cache Rules**.

   <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

2. Select the **Cache Response Rules** tab.
3. Select **Create rule**.

4. Enter a descriptive name for the rule in **Rule name**.
5. Under **When incoming requests match**, select **All incoming requests** if you want the rule to apply to all traffic or **Custom filter expression** if you want the rule to only apply to traffic matching the custom expression.
6. If you selected **Custom filter expression**, define the [rule expression](/ruleset-engine/rules-language/expressions/edit-expressions/#expression-builder). Use the **Field** drop-down list to choose an HTTP property and select an **Operator**. Both request fields (such as URI path or hostname) and response fields (such as response status code or response headers) are available for matching. Refer to [Available settings](/cache/how-to/cache-response-rules/settings/) for the full list of available fields and operators.

   :::note
   Rules can be further customized by using the **Edit expression** option. You can find more information in [Edit expressions in the dashboard](/ruleset-engine/rules-language/expressions/edit-expressions/).
   :::

7. Following the selection of the field and operator, enter the corresponding value that will trigger the Cache Response Rule. For example, if the selected field is `Hostname` and the operator is `equals`, a value of `example.com` would mean the rule matches any request to that hostname. If an [Origin Rule](/rules/origin-rules/) rewrites the `Host` header, enter the hostname your origin receives instead, otherwise the rule will not match.
8. Under **Then**, select one of the following actions:

   - **Modify cache-control directives**: Set or remove `Cache-Control` directives sent by your origin. For each directive, choose **Set directive** or **Remove directive**. For duration-based directives like `max-age` or `s-maxage`, enter a value in seconds. Turn on **Cloudflare only** to apply the directive only within Cloudflare's cache without changing what visitors receive. Refer to [Supported directives](/cache/how-to/cache-response-rules/settings/#supported-directives) for the full list.

   - **Modify cache tags**: Add, override, or remove cache tags on the response for targeted [purging](/cache/how-to/purge-cache/purge-by-tags/). Select one of the following operations:
     - **Add to existing tags**: Append new tags to the current set.
     - **Override existing tags**: Replace all current tags with the specified tags.
     - **Remove from existing tags**: Remove specific tags from the current set.

     For the tag source, you can either specify tags manually or select **Parse from response header** to extract tags from a response header value. When parsing from a header, you can split the header value using a custom separator (for example, commas instead of spaces).

   - **Strip headers**: Remove `Set-Cookie`, `ETag`, or `Last-Modified` headers from the origin response before Cloudflare evaluates the response for caching. Select which headers to strip.

   For more details on each action, refer to [Available settings](/cache/how-to/cache-response-rules/settings/#available-actions).
9. Under **Place at**, from the dropdown, you can select the order of your rule. From the main page, you can also change the order of the rules you have created.
10. To save and deploy your rule, select **Deploy**. If you are not ready to deploy your rule, select **Save as Draft**.

   <Render file="rules-creation-dash-dns-popup" product="rules" />

# Cache Response Rules

import { InlineBadge, Render } from "~/components";

Cache Response Rules allow you to configure cache settings based on request and response attributes. These rules execute prior to caching in the `http_response_cache_settings` phase, which runs after Cloudflare receives the origin response.

:::note
Cache Response Rules evaluate the request as it is sent to your origin. If an [Origin Rule](/rules/origin-rules/) rewrites the `Host` header, expressions that match on `http.host` must use the rewritten hostname. The equivalent Cache Rule still sees the original hostname because Cache Rules run before Origin Rules are applied.
:::

With Cache Response Rules you can:

- Modify `Cache-Control` directives sent by your origin.
- Modify cache tags on responses for targeted [cache purging](/cache/how-to/purge-cache/).
- Strip headers (`ETag`, `Set-Cookie`, `Last-Modified`) from origin responses before caching.

Cache Response Rules apply to both cached and non-cached (dynamic) responses from the origin. For example, you can strip Set-Cookie headers from responses that are not eligible for caching.

Cache Response Rules can be created in the [dashboard](/cache/how-to/cache-response-rules/create-dashboard/), via [API](/cache/how-to/cache-response-rules/create-api/), or [Terraform](/cache/how-to/cache-response-rules/terraform-example/).

:::note
Cache Response Rules require that you [proxy the DNS records](/dns/proxy-status/) of your domain (or subdomain) through Cloudflare.
:::

## Availability

The following table describes Cache Response Rules availability per plan.

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |
| Number of rules | 10 | 25 | 50 | 300 |

<Render
	file="troubleshoot-rules-with-trace"
	product="rules"
	params={{ rulesFeatureName: "Cache Response Rules" }}
/>

## Relationship with Cache Rules

Cache Response Rules operate on the origin response, while [Cache Rules](/cache/how-to/cache-rules/) operate on the incoming request. When settings from both rule types conflict, Cache Response Rules take precedence.

Key differences:

- **Cache eligibility**: Cache Rules remain the only mechanism to decide whether content is eligible for caching. However, Cache Response Rules can make a cacheable asset non-cacheable by setting the `no-store` directive using the `set_cache_control` action.
- **Origin Cache Control (OCC)**: If any rule in the `http_response_cache_settings` phase matches, Cloudflare defaults to Origin Cache Control behavior (`origin_cache_control = true`).
- **CDN-Cache-Control precedence**: `Cache-Control` directives set by Cache Response Rules take precedence over origin-set `Cloudflare-CDN-Cache-Control` and `CDN-Cache-Control` headers. For more information, refer to [CDN-Cache-Control header precedence](/cache/concepts/cdn-cache-control/#header-precedence).
- **Stacking**: Cache Response Rules stack the same way as Cache Rules. When multiple rules specify the same setting, the last matching rule wins.

### Example: OCC precedence over Edge TTL

Consider the following scenario:

1. A Cache Rule sets **Edge TTL** to `override_origin` with a value of `7200` seconds (2 hours).
2. A Cache Response Rule uses `set_cache_control` to set `s-maxage` to `3600` seconds (1 hour) with `cloudflare_only` enabled.
3. The origin responds with `Cache-Control: s-maxage=600`.

In this case, the Cache Response Rule takes precedence. Cloudflare caches the asset for `3600` seconds (1 hour) based on the `s-maxage` directive set by the Cache Response Rule, while visitors still receive the original `s-maxage=600` from the origin because `cloudflare_only` is enabled.

## Difference from Workers and Transform Rules

Workers and [Response Header Transform Rules](/rules/transform/response-header-modification/) execute after the caching decision has been made and cannot influence whether or how a response is cached. Only [Cache Response Rules](/cache/how-to/cache-response-rules/) can modify caching behavior based on origin response headers.

If you need to override `Cache-Control` directives from the origin (for example, remove `private` or add `s-maxage`), use a [Cache Response Rule](/cache/how-to/cache-response-rules/) — not a Worker or Transform Rule.

## Notes

- If you strip last modified then Smart Edge Revalidation will be turned off.
- Cache Response Rules ignore [`1xx` HTTP response status codes](/support/troubleshooting/http-status-codes/1xx-informational/) as they are treated as informational responses.
- Cache Response Rules can be versioned. Refer to the [Version Management](/version-management/) documentation for more information.

# Available settings

import { Details } from "~/components";

These are the settings that you can configure when creating a Cache Response Rule. Because Cache Response Rules execute after Cloudflare receives the origin response, both request and response fields are available for rule matching.

:::note
Request fields describe the request as Cloudflare sends it to your origin. If an [Origin Rule](/rules/origin-rules/) changes the `Host` header, `http.host` returns the changed value. Match on the hostname your origin receives, not the hostname visitors request.
:::

## Expression fields

### Request fields

| Field | Type | Description |
| --- | --- | --- |
| `http.cookie` | String | Full cookie header value |
| `http.host` | String | The `Host` header received by your origin, which can differ from the requested hostname if an [Origin Rule](/rules/origin-rules/) changed it |
| `http.referer` | String | The HTTP Referer header |
| `http.user_agent` | String | The HTTP User-Agent header |
| `http.request.method` | String | The HTTP request method |
| `http.request.uri` | String | The request URI |
| `http.request.uri.path` | String | The URI path |
| `http.request.uri.path.basename` | String | The basename of the URI path |
| `http.request.uri.path.extension` | String | The file extension from the URI path |
| `http.request.uri.query` | String | The query string |
| `http.request.uri.args` | Map | Query string arguments as key-value pairs |
| `http.request.uri.args.names` | Array | Query string argument names |
| `http.request.uri.args.values` | Array | Query string argument values |
| `http.request.full_uri` | String | The full request URI including scheme and host |
| `http.request.headers` | Map | Request headers as key-value pairs |
| `http.request.headers.names` | Array | Request header names |
| `http.request.headers.values` | Array | Request header values |
| `http.request.cookies` | Map | Parsed cookies as key-value pairs |
| `http.request.accepted_languages` | Array | Parsed Accept-Language header values |

### Response fields

| Field | Type | Description |
| --- | --- | --- |
| `http.response.code` | Integer | The HTTP response status code from the origin |
| `http.response.headers` | Map | Response headers as key-value pairs |
| `http.response.headers.names` | Array | Response header names |
| `http.response.headers.values` | Array | Response header values |

If you select the [Edit expression](/ruleset-engine/rules-language/expressions/edit-expressions/#expression-editor) option, you can enter any of the above response fields.

## Functions

The following functions are available in this phase:

- all
- any
- concat
- decode_base64
- ends_with
- len
- lookup_json_integer
- lookup_json_string
- lower
- regex_replace
- remove_bytes
- remove_query_args
- split
- starts_with
- substring
- to_string
- upper
- url_decode
- wildcard_replace

For descriptions of each function, refer to [Functions](/ruleset-engine/rules-language/functions/).

## Operators

For the full list of operators, refer to [Operators](/ruleset-engine/rules-language/operators/).

## Available actions

Cache Response Rules support three actions:

| Action | Description |
| --- | --- |
| `set_cache_settings` | Strip headers (ETags, Set-Cookie, Last-Modified) from the origin response before caching. |
| `set_cache_tags` | Add, remove, or set cache tags on the response for targeted purging. |
| `set_cache_control` | Modify Cache-Control header directives in the origin response. |

---

### Action: set_cache_settings

Configures settings related to caching on the origin response. The following parameters are available:

| Parameter | Type | Description |
| --- | --- | --- |
| `strip_etags` | Boolean | Strip ETag headers from the origin response before caching. |
| `strip_set_cookie` | Boolean | Strip Set-Cookie headers from the origin response before caching. |
| `strip_last_modified` | Boolean | Strip Last-Modified headers from the origin response before caching. |

:::note

If `strip_etags` or `strip_last_modified` is `true` after all matching rules are applied, [Smart Edge Revalidation](https://blog.cloudflare.com/introducing-smart-edge-revalidation/) is disabled for the origin response.

:::

<Details header="API information">

API action: `set_cache_settings`.

```json title="API configuration example"
"action_parameters": {
  "strip_etags": true,
  "strip_set_cookie": true,
  "strip_last_modified": true
}
```

Refer to [Create a rule via API](/cache/how-to/cache-response-rules/create-api/) for complete API examples.

</Details>

---

### Action: set_cache_tags

Modifies the cache tags associated with the response. Cache tags can be used for targeted [cache purging](/cache/how-to/purge-cache/purge-by-tags/).

| Parameter | Type | Description |
| --- | --- | --- |
| `operation` | String | **Required.** One of: `add`, `remove`, `set`. |
| `values` | Array | A list of cache tag strings. Mutually exclusive with `expression`. |
| `expression` | String | An expression that evaluates to an array of cache tags. Mutually exclusive with `values`. |

<Details header="API information">

API action: `set_cache_tags`.

```json title="API configuration example (static values)"
"action_parameters": {
  "operation": "set",
  "values": ["api-response", "dynamic-content"]
}
```

```json title="API configuration example (expression)"
"action_parameters": {
  "operation": "add",
  "expression": "split(http.response.headers[\"Surrogate-Keys\"][0], \",\", 1)"
}
```

Refer to [Create a rule via API](/cache/how-to/cache-response-rules/create-api/) for complete API examples.

</Details>

---

### Action: set_cache_control

Modifies Cache-Control header directives in the origin response.

#### Supported directives

**Directives with duration value (seconds):**

- `max-age`
- `s-maxage`
- `stale-if-error`
- `stale-while-revalidate`

**Directives with optional qualifiers (header names):**

- `private`
- `no-cache`

**Boolean directives:**

- `no-store`
- `no-transform`
- `must-revalidate`
- `proxy-revalidate`
- `must-understand`
- `public`
- `immutable`

#### Directive configuration

The available parameters depend on the directive type.

##### Directives with duration value

Applies to `max-age`, `s-maxage`, `stale-if-error`, and `stale-while-revalidate`.

| Parameter | Type | Description |
| --- | --- | --- |
| `operation` | String | **Required.** `set` or `remove`. |
| `cloudflare_only` | Boolean | When enabled, this setting only affects how Cloudflare caches your content. Your visitors still receive the original directive. |
| `value` | Integer | Duration in seconds. **Required when operation is `set`.** |

##### Directives with optional qualifiers

Applies to `private` and `no-cache`.

| Parameter | Type | Description |
| --- | --- | --- |
| `operation` | String | **Required.** `set` or `remove`. |
| `cloudflare_only` | Boolean | When enabled, this setting only affects how Cloudflare caches your content. Your visitors still receive the original directive. |
| `qualifiers` | Array | Optional list of header names to qualify the directive. |

##### Boolean directives

Applies to `no-store`, `no-transform`, `must-revalidate`, `proxy-revalidate`, `must-understand`, `public`, and `immutable`.

| Parameter | Type | Description |
| --- | --- | --- |
| `operation` | String | **Required.** `set` or `remove`. |
| `cloudflare_only` | Boolean | When enabled, this setting only affects how Cloudflare caches your content. Your visitors still receive the original directive. |

<Details header="API information">

API action: `set_cache_control`.

```json title="API configuration example"
"action_parameters": {
  "max-age": {
    "operation": "set",
    "value": 3600,
    "cloudflare_only": true
  },
  "stale-if-error": {
    "operation": "remove"
  }
}
```

Refer to [Create a rule via API](/cache/how-to/cache-response-rules/create-api/) for complete API examples.

</Details>

# Terraform example

import { Details, Render } from "~/components";

The following example defines a single Cache Response Rule for a zone using Terraform. The rule strips Set-Cookie and ETag headers from JavaScript file responses before caching.

<Details header="Terraform `cloudflare_ruleset` resource">

```tf
# Cache Response Rule to strip headers from JS responses
resource "cloudflare_ruleset" "cache_response_rules_example" {
  zone_id     = "<ZONE_ID>"
  name        = "Cache Response Rules"
  description = "Configure cache settings for origin responses"
  kind        = "zone"
  phase       = "http_response_cache_settings"

  rules {
    ref         = "strip_js_headers"
    description = "Strip caching headers from JS file responses"
    expression  = "http.request.uri.path.extension eq \"js\""
    action      = "set_cache_settings"
    action_parameters {
      strip_etags      = true
      strip_set_cookie = true
    }
  }

  rules {
    ref         = "tag_api_responses"
    description = "Tag API responses for targeted purging"
    expression  = "starts_with(http.request.uri.path, \"/api/\")"
    action      = "set_cache_tags"
    action_parameters {
      operation = "set"
      values    = ["api-response", "dynamic-content"]
    }
  }

  rules {
    ref         = "cache_control_200"
    description = "Set cache-control for successful responses"
    expression  = "http.response.code eq 200"
    action      = "set_cache_control"
    action_parameters {
      s_maxage {
        operation      = "set"
        value          = 86400
        cloudflare_only = true
      }
      must_revalidate {
        operation = "set"
      }
    }
  }
}
```

<Render
	file="terraform-use-ref-field-with-location"
	product="rules"
/>

</Details>

For additional guidance on using Terraform with Cloudflare, refer to [Terraform](/terraform/).

# Create a rule via API

import { Details, APIRequest } from "~/components"

Use the [Rulesets API](/ruleset-engine/rulesets-api/) to create a cache rule via API. To configure Cloudflare’s API refer to the [API documentation](/fundamentals/api/get-started/).

## Basic rule settings

When creating a cache rule via API, make sure you:

* Set the rule action to `set_cache_settings`.
* Define the parameters in the `action_parameters` field according to the [settings](/cache/how-to/cache-rules/settings/) you wish to override for matching requests.
* Deploy the rule to the `http_request_cache_settings` phase entry point ruleset.

## Procedure

1. Use the [List zone rulesets](/api/resources/rulesets/methods/list/) method to obtain the list of rules already present in the `http_request_cache_settings` phase entry point ruleset.
2. If the phase ruleset does not exist, create it using the [Create a zone ruleset](/api/resources/rulesets/methods/create/) operation. In the new ruleset properties, set the following values:
   * kind: `zone`
   * phase: `http_request_cache_settings`
3. Use the [Update a zone ruleset](/api/resources/rulesets/methods/update/) operation to add a cache rule to the list of ruleset rules. Alternatively, include the rule in the [Create a zone ruleset](/api/resources/rulesets/methods/create/) request mentioned in the previous step.
4. (Optional) To update an existing cache rule, use the [Update a zone ruleset rule](/api/resources/rulesets/methods/update/) operation. For an example, refer to the section below.

## Example requests

These examples are setting all the Cache Rules of a zone to a single rule, since using these examples directly will cause any existing rules to be deleted.


<Details header="Example: Cache everything for example.com">

<APIRequest
  path="/zones/{zone_id}/rulesets/{ruleset_id}"
  method="PUT"
  json={{
    rules: [
      {
        expression: '(http.host eq "example.com")',
        description: "cache everything for example.com",
        action: "set_cache_settings",
        action_parameters: {
          cache: true
        }
      }
    ]
  }}
  roles={false}
/>


</Details>


<Details header="Example: Extend read timeout for Android clients">

<APIRequest
  path="/zones/{zone_id}/rulesets/{ruleset_id}"
  method="PUT"
  json={{
    rules: [
      {
        expression: '(http.user_agent contains "Android")',
        description: "extend read timeout for android clients",
        action: "set_cache_settings",
        action_parameters: {
          cache: true,
          read_timeout: 300
        }
      }
    ]
  }}
  roles={false}
/>

</Details>


<Details header="Example: Disable Cache Reserve for frequently updated assets">

<APIRequest
  path="/zones/{zone_id}/rulesets/{ruleset_id}"
  method="PUT"
  json={{
    rules: [
      {
        expression: '(starts_with(http.request.uri, "/feed/"))',
        description: "disable cache reserve for frequently updated assets",
        action: "set_cache_settings",
        action_parameters: {
          cache: true,
          cache_reserve: {
            enabled: false
          }
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details header="Example: Turn on Origin Range Requests for large media files">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_request_cache_settings" }}
  json={{
    rules: [
      {
        expression:
          '(http.request.uri.path.extension in {"mp4" "mov" "m4v"})',
        description: "turn on origin range requests for large media files",
        action: "set_cache_settings",
        action_parameters: {
          cache: true,
          origin_range_requests: {
            mode: "on"
          }
        }
      }
    ]
  }}
  roles={false}
/>

</Details>

<Details
  header="Example: Turn off default Origin Range Requests"
  id="example-turn-off-default-origin-range-requests"
>

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_request_cache_settings" }}
  json={{
    rules: [
      {
        expression: "true",
        description: "turn off default Origin Range Requests",
        action: "set_cache_settings",
        action_parameters: {
          origin_range_requests: {
            mode: "off"
          }
        }
      }
    ]
  }}
  roles={false}
/>

</Details>


<Details header="Example: Turn off default cache TTLs">

<APIRequest
  path="/zones/{zone_id}/rulesets/{ruleset_id}"
  method="PUT"
  json={{
    rules: [
      {
        expression: '(http.host eq "example.com")',
        description: "turn off default cache ttls",
        action: "set_cache_settings",
        action_parameters: {
          cache: true,
          edge_ttl: {
            mode: "bypass_by_default"
          }
        }
      }
    ]
  }}
  roles={false}
/>

</Details>


<Details header="Example: Cache expected Vary responses">

<APIRequest
  path="/zones/{zone_id}/rulesets/phases/{ruleset_phase}/entrypoint"
  method="PUT"
  parameters={{ ruleset_phase: "http_request_cache_settings" }}
  json={{
    rules: [
      {
        expression: '(http.host eq "example.com")',
        description: "cache expected vary responses",
        action: "set_cache_settings",
        action_parameters: {
          cache: true,
          vary: {
            default: {
              action: "bypass"
            },
            headers: {
              accept: {
                action: "normalize",
                media_types: ["text/html", "application/json"]
              },
              "accept-language": {
                action: "normalize",
                languages: ["en", "fr", "de"]
              }
            }
          }
        }
      }
    ]
  }}
  roles={false}
/>


</Details>


<Details header="Example: Update the position of an existing rule">

<APIRequest
  path="/zones/{zone_id}/rulesets/{ruleset_id}/rules/{rule_id}"
  method="PATCH"
  json={{
    expression: '(http.host eq "example.com")',
    description: "cache everything for example.com",
    action: "set_cache_settings",
    action_parameters: {
      cache: true
    },
    enabled: true,
    position: {
      before: "da5e8e506c8e7877fe06cdf4c41add54"
    }
  }}
  roles={false}
/>


</Details>

## Required API token permissions

The API token used in API requests to manage Cache Rules must have the following permissions:

* *Zone* > *Cache Rules* > *Edit*
* *Account Rulesets* > *Edit*
* *Account Filter Lists* > *Edit*

# Create a rule in the dashboard

import { Render, DashButton } from "~/components";

1. In the Cloudflare dashboard, go to the **Cache Rules** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

2.  Select **Create rule**.
3.  (Optional) Select one of the rule templates that address common use cases. Then, review and adjust the proposed rule configuration.
4.  Enter a descriptive name for the rule in **Rule name**.
5.  Under **When incoming requests match**, select **All incoming requests** if you want the rule to apply to all traffic or **Custom filter expression** if you want the rule to only apply to traffic matching the custom expression.
6.  If you selected **Custom filter expression**, under **When incoming requests match**, define the [rule expression](/ruleset-engine/rules-language/expressions/edit-expressions/#expression-builder). Use the **Field** drop-down list to choose an HTTP property and select an **Operator**. Refer to [Available settings](/cache/how-to/cache-rules/settings/) for the list of available fields and operators.

    <div class="medium-img">

    ![Select fields in the Expression Builder.](~/assets/images/cache/select-fields.png)

    </div>

8.  Following the selection of the field and operator, enter the corresponding value that will trigger the Cache Rule. For example, if the selected field is `Hostname` and the operator is `equals`, a value of `cloudflare.com` would mean the rule matches any request to that hostname.

    <div class="medium-img">

    ![Example rule](~/assets/images/cache/example-rule.png)

    </div>

    :::note
    Rules can be further customized by using the **Edit expression** option. You can find more information in [Edit expressions in the dashboard](/ruleset-engine/rules-language/expressions/edit-expressions/).
    :::

9.  Under **Then**, in the **Cache eligibility** section, select [**Bypass cache**](/cache/how-to/cache-rules/settings/#bypass-cache) if you want matching requests to not be cacheable, or **Eligible for cache** if you want Cloudflare to attempt to cache them. Note that [cache-control headers](/cache/concepts/cache-control/) can also impact cache eligibility.

10. If you selected **Eligible for cache** in the previous step, you can customize the options described in the [Available settings](/cache/how-to/cache-rules/settings/) section.

11. Under **Place at**, from the dropdown, you can select the order of your rule. From the main page, you can also change the order of the rules you have created.

12. To save and deploy your rule, select **Deploy**. If you are not ready to deploy your rule, select **Save as Draft**.

    <Render file="rules-creation-dash-dns-popup" product="rules" />

# Cache Rules

import { Render } from "~/components";

Use Cache Rules to customize cache settings on Cloudflare. Cache Rules allows you to make adjustments to what is eligible to cache, how long it should be cached and where, as well as trigger specific interactions with Cloudflare's cache and other Rules products for matching requests.

Cache Rules can be created in the [dashboard](/cache/how-to/cache-rules/create-dashboard/), via [API](/cache/how-to/cache-rules/create-api/) or [Terraform](/cache/how-to/cache-rules/terraform-example/).

:::note[Notes]

Cache Rules require that you [proxy the DNS records](/dns/proxy-status/) of your domain (or subdomain) through Cloudflare.

Rules can be versioned. Refer to the [Version Management](/version-management/) documentation for more information.

:::

## Rules templates

<Render file="rules-templates" product="rules" />

## Availability

The following table describes Cache Rules availability per plan.

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |
| Number of rules | 10 | 25 | 50 | 300 |

## Cache Rules and cache keys

When a Cache Rule sets a [custom cache key](/cache/how-to/cache-keys/), the resulting cache entry is indexed by that key rather than the request URL alone. Depending on what the custom cache key includes, this may affect [single-file purge](/cache/how-to/purge-cache/purge-by-single-file/):

- **Custom cache keys that only change how the query string is handled** (for example, ignoring the query string) generally work with dashboard single-file purge.
- **Custom cache keys that include headers, cookies, or other request properties** will prevent dashboard single-file purge from working, because the dashboard cannot send those values in a purge request.
- **Even without Cache Rules**, Cloudflare's default cache key includes certain request headers. Dashboard single-file purge may not work for resources cached with those headers present.

To purge resources that cannot be cleared via dashboard single-file purge, you have the following options:

- Use the API to [purge by URL](/api/resources/cache/methods/purge/#purge-cached-content-by-url), including all headers, cookies, and query strings that are part of your custom cache key. If any header or cookie is missing from the purge request, it is treated as an empty value in the cache key.
- [Purge by host](/cache/how-to/purge-cache/purge-by-hostname/), which clears all resources for a hostname and is not affected by custom cache keys.
- [Purge by prefix](/cache/how-to/purge-cache/purge_by_prefix/), which purges all resources under a URL path and is not affected by custom cache keys.
- [Purge by tag](/cache/how-to/purge-cache/purge-by-tags/), which is not affected by custom cache keys.
- [Purge everything](/cache/how-to/purge-cache/purge-everything/), which clears all cached resources for the zone.

For more information, refer to [Cache keys](/cache/how-to/cache-keys/) and [Purge cache key resources](/cache/how-to/purge-cache/purge-cache-key/).

<Render
	file="troubleshoot-rules-with-trace"
	product="rules"
	params={{ rulesFeatureName: "Cache Rules" }}
/>

# Order and priority

import { Render } from "~/components"

Cache rules affect requests differently from Page Rules. This is how they are applied:

1. Cache Rules are stackable. This means that multiple matching rules can be combined and applied to the same request. For example, if multiple cache rules match the same URL, then the features set in those cache rules will all be applied in order. If several matching rules set a value for the same setting, the value in the last matching rule wins. For an example of a similar scenario where multiple rules match, refer to the [Origin Rules FAQ](/rules/origin-rules/faq/#what-happens-if-more-than-one-origin-rule-matches-the-current-request).

2. For conflicting settings (for example, bypass cache versus eligible for cache), the last matching rule wins. For example, if cache rule #1 is set to cache everything on `example.com/images` and cache rule #2 is set to bypass cache on `example.com`, then cache will be bypassed for all URLs that match `example.com`, since rule #2 is the last matching rule.

3. If you have Page Rules implemented for caching on the same path, Cache Rules will take precedence by design.

4. Cache rules can be more specific than website-wide settings in the cache configuration tab, so they take precedence over website-wide settings on requests they match against. For example, if browser cache TTL is set to 4 hours for the entire website `example.com` and there is a cache rule matching requests with a path of `/feed` setting browser cache TTL to 10 seconds, the cache rule will override the website-wide setting for requests to `https://example.com/feed`.

## Execution order of Rules products

<Render file="product-execution-order" product="rules" />

# Migration from Page Rules

import { TabItem, Tabs } from "~/components"

If you are migrating from Page Rules, there is a behavior change between Page Rules and Cache Rules.

When you create a new Cache Rule and select **Eligible for cache**, the Cache Everything feature is enabled by default. With Page Rules, you had to specifically enable the Cache Everything option.

To maintain the same behavior you had with Page Rules (that is, not enabling Cache Everything), you need to create these two specific rules in this order before creating any additional rules.

Multiple matching cache rules can be combined and applied to the same request. After rule 1 matches, Cloudflare will keep evaluating other cache rules checking for matches. For more information, refer to [Order and priority](/cache/how-to/cache-rules/order/).

## Rule 1

<Tabs> <TabItem label="Dashboard">

1. Enter a rule name, for instance `bypass everything`.
2. In **When incoming requests match**, select **All incoming requests**.
3. Under **Then**, in the **Cache eligibility** section, select [Bypass cache](/cache/how-to/cache-rules/settings/#bypass-cache).

</TabItem> <TabItem label="visual guide">

![Create rule to bypass cache](~/assets/images/cache/first-rule.png)

</TabItem> </Tabs>

## Rule 2

<Tabs> <TabItem label="Dashboard">

1. Enter a rule name, for instance `cache all default cacheable extensions`.
2. In **When incoming requests match**, select **Custom filter expression**.
3. Define the following rule:
   * **Field**: `File extension`
   * **Operator**: `is in`
   * **Value**: `7z, avi, avif, apk, bin, bmp, bz2, class, css, csv, doc, docx, dmg, ejs, eot, eps, exe, flac, gif, gz, ico, iso, jar, jpg, jpeg, js, mid, midi, mkv, mp3, mp4, ogg, otf, pdf, pict, pls, png, ppt, pptx, ps, rar, svg, svgz, swf, tar, tif, tiff, ttf, webm, webp, woff, woff2, xls, xlsx, zip, zst`

If you prefer, you can select **Edit expression** and paste the following expression:

```txt
(http.request.uri.path.extension in {"7z" "avi" "avif" "apk" "bin" "bmp" "bz2" "class" "css" "csv" "doc" "docx" "dmg" "ejs" "eot" "eps" "exe" "flac" "gif" "gz" "ico" "iso" "jar" "jpg" "jpeg" "js" "mid" "midi" "mkv" "mp3" "mp4" "ogg" "otf" "pdf" "pict" "pls" "png" "ppt" "pptx" "ps" "rar" "svg" "svgz" "swf" "tar" "tif" "tiff" "ttf" "webm" "webp" "woff" "woff2" "xls" "xlsx" "zip" "zst"})
```

4. Under **Then**, in the **Cache eligibility** section, select [**Eligible for cache**](/cache/how-to/cache-rules/settings/#eligible-for-cache-settings).

</TabItem> <TabItem label="visual guide">

![Create an eligible for cache rule](~/assets/images/cache/second-rule.png)

</TabItem> </Tabs>

:::note[Note]


Remember to create the rules in the specified order: first, the `bypass everything` rule, and then the `cache all default cacheable file extensions` rule.

![Rules order](~/assets/images/cache/rule-order.png)


:::

# Available settings

import { Details } from "~/components";

These are the settings that you can configure when creating a cache rule.

## Fields

The fields available for Cache Rule matching expressions in the **Expression Builder** are:

- URI Full - `http.request.full_uri`
- URI - `http.request.uri`
- URI Path - `http.request.uri.path`
- URI Query String - `http.request.uri.query`
- Cookie - `http.cookie`
- Hostname - `http.host`
- Referer - `http.referer`
- SSL/HTTPS - `ssl`
- User Agent - `http.user_agent`
- X-Forwarded-For - `http.x_forwarded_for`
- Request Headers - `http.request.headers`
- Cookie value of - `http.request.cookies`
- File extension - `http.request.uri.path.extension`

If you select the [Edit expression](/ruleset-engine/rules-language/expressions/edit-expressions/#expression-editor) option, you can enter additional fields supported by Cache Rules. These fields include:

- `cf.bot_management.score`
- `cf.bot_management.ja3_hash`
- `cf.bot_management.ja4`
- `cf.bot_management.verified_bot`
- `cf.bot_management.static_resource`
- `cf.bot_management.js_detection.passed`
- `cf.bot_management.detection_ids`
- `cf.bot_management.tags`
- `cf.bot_management.signed_agent`
- `cf.bot_management.corporate_proxy`
- `ip.src.asnum`

Bot Management fields require a [Bot Management subscription](/bots/plans/bm-subscription/). For field types, descriptions, and expression syntax, refer to the [Fields reference](/ruleset-engine/rules-language/fields/reference/).

:::note

[Single-file purge](/cache/how-to/purge-cache/purge-by-single-file/) may not work as expected if your Cache Rule sets a [custom cache key](/cache/how-to/cache-keys/) (because the dashboard cannot send the headers or cookies that are part of the key), or if your rule expression matches only `GET` requests (because purge requests use a different HTTP method internally).

For more information and alternatives, refer to [Purge by single-file limitations](/cache/how-to/purge-cache/purge-by-single-file/#limitations-and-alternatives).

:::

## Operators

The operators available for Cache Rule expressions are:

- wildcard
- strict wildcard
- equals
- does not equal
- contains
- does not contain
- matches regex
- does not match regex
- starts with
- ends with
- does not start with
- does not end with
- is in
- is not in
- is in list
- is not in list

:::note

Not all operators are available for every selected field.
:::

:::note

The **is in list** and **is not in list** operators are available for the Hostname field on Enterprise plans. They allow you to match requests against [custom lists](/waf/tools/lists/custom-lists/) — for example, a hostname list containing all the domains you want to apply a particular caching configuration to. Refer to [Use lists in expressions](/waf/tools/lists/use-in-expressions/) for details on using lists in rule expressions.
:::

## Cache eligibility

In **Cache eligibility**, you have the option to select **Bypass cache** if you want matching requests to not be cached, or **Eligible for cache** if you want Cloudflare to attempt to cache them.

### Bypass cache

When creating a cache rule, you have the option to select **Bypass cache** if you want matching incoming requests to not be cached. Alternatively, you can use [Development Mode](/cache/reference/development-mode/), if you want to bypass cache for shorter periods.

:::note
When using Custom Cache Rules with a Bypass setting, the response header may return [DYNAMIC](/cache/concepts/cache-responses/#dynamic) rather than explicitly indicating a bypass. This occurs because either the asset is originally ineligible for caching or the rule makes it ineligible for caching, even if the origin response is otherwise cacheable.
:::

### Eligible for cache settings

When you select **Eligible for cache**, you can change the configuration settings described below.

:::note

If you use cache rules, image transformations, and zone versioning simultaneously, some settings may not be applied correctly.
:::

#### Edge TTL

Edge Cache TTL refers to the maximum cache time-to-live (TTL), or how long an asset should be considered fresh or available to serve from Cloudflare’s cache in response to requests. This setting has three primary options:

- **Use cache control-header if present, bypass cache if not**: If a cache-control header is present on the response, follow its directives. If not, skip caching entirely.
- **Use cache-control header if present, use default Cloudflare caching behavior if not**: If a cache-control header is present on the response, follow its directives. If not, cache in accordance with our [default edge TTL settings](/cache/how-to/configure-cache-status-code/#edge-ttl).
- **Ignore cache-control header and use this TTL**: Completely ignore any cache-control header on the response and instead cache the response for a duration specified in the timing dropdown.

Additionally, you can select how long you would like a particular matching status code's content to be cached in Cloudflare's global network. In **Status Code TTL** section you can define the TTL duration for one or more status codes of responses from the origin server. This setting can be applied to a _Single code_ status code, to a _Greater than or equal_ or _Less than or equal_ status code, or to a _Range_ of status codes. Status code TTLs are similar to **Ignore cache-control header and use this TTL** in that the cache-control header on the response will be ignored in favor of the TTL specified by the cache rule. For more information, refer to [Status code TTL](/cache/how-to/configure-cache-status-code/).

<Details header="API information">

API configuration object name: `"edge_ttl"`.

| API values          | Configuration                                                                                                                   |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `respect_origin`    | Use cache-control header if present, use default [Cloudflare caching behavior](/cache/concepts/default-cache-behavior/) if not. |
| `override_origin`   | Ignore cache-control header and use this TTL.                                                                                   |
| `bypass_by_default` | Use cache control-header if present, bypass cache if not.                                                                       |
|                     |                                                                                                                                 |

```json title="API configuration example"
"action_parameters": {
    "cache": true,
    "edge_ttl": {
        "status_code_ttl": [
            {
                "status_code_range": {
                    "to": 299
                },
                "value": 86400
            },
            {
                "status_code_range": {
                    "from": 300,
                    "to": 499
                },
                "value": 0  // no-cache
            },
            {
                "status_code_range": {
                    "from": 500
                },
                "value": -1  // no-store
            }
        ],
        "mode": "respect_origin"
    }
}


```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Browser TTL

Browser TTL refers to the maximum cache time-to-live (TTL) that an asset should be considered available to serve from the browser’s cache.

Select if you want to **Bypass cache**, **Respect origin**, or **Override origin**. If you wish to override the browser TTL value, define how long resources cached by client browsers will remain valid from the dropdown menu. For more information, refer to [Browser Cache TTL](/cache/how-to/edge-browser-cache-ttl/#browser-cache-ttl).

<Details header="API information">

API configuration object name: `"browser_ttl"`.

API values for the `"mode"` property: `"respect_origin"`, `"override_origin"`, `"bypass_by_default"`. <br/>

API values for the `"default"` property (integer): values available depend on your plan. Refer to [Browser Cache TTL](/cache/how-to/edge-browser-cache-ttl/#browser-cache-ttl).

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "browser_ttl" : {
    "mode": "override_origin",
    "default": 1000
  }
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Cache Key

Cache keys refer to the criteria that Cloudflare uses to determine how to store resources in our cache. Customizing the Cache Key allows you to determine how Cloudflare can reuse particular cache entries across requests or share the cache entries for more granularity for end users.

There is no explicit length limit for cache keys. However, the total request size (including headers used in the cache key) must not exceed Cloudflare's [request limits](/fundamentals/reference/connection-limits/#request-limits). Including large values (such as cookies) in the cache key may increase per-request latency. The maximum number of query string parameters in a custom cache key configuration is 100.

Define the request components used to define a [custom Cache Key](/cache/how-to/cache-keys/), customizing the following options:

- You can switch on or off [Cache deception armor](/cache/cache-security/cache-deception-armor/), [Cache by device type](/automatic-platform-optimization/reference/cache-device-type/), and [Sort query string](/cache/how-to/cache-keys/#query-string).

Enterprise customers have these additional options for custom Cache Keys:

- In the **Query string** section, you can select **All query string parameters**, **All query string parameters except** and enter an exception, **No query parameters except** and enter the parameters, or **Ignore query string** (also available for Pay-as-you-go customers).
- In the **Headers** section, you can specify header names along with their values. For custom headers, values are optional; however, for the following restricted headers, you must include one to three specific values:
  - `accept`
  - `accept-charset`
  - `accept-encoding`
  - `accept-datetime`
  - `accept-language`
  - `referer`
  - `user-agent`

  To check for a header's presence without including its value, use the **Check presence of** option. You can also choose whether to **Include origin header**.

- In the **Cookie** section, you can include cookie names and their values, and check for the presence of another cookie.
- In the **Host** section, you can select **Use original host** and **Resolved host**. In the **User** section, you can select **Device type**, **Country**, and **Language**. Using **Resolved host** means the Cache Key will contain whatever hostname was used to resolve the origin IP which can be different depending on whether the [resolve override](/rules/origin-rules/features/#dns-record) feature is on or not.

:::note
When [URL normalization](/rules/normalization/) is enabled, we recommend also enabling [Normalize URLs to origin](/rules/normalization/manage/), especially if you are setting custom Cache Keys or using cache by device type, which also modifies the Cache Key. This helps ensure the URL in the Cache Key matches the URL sent to the origin, preventing cache poisoning and ensuring consistent behavior.
:::

<Details header="API information">

API configuration object name: `"cache_key"`.

API values: `"ignore_query_strings_order"`, `"cache_deception_armor"`, `"cache_by_device_type"`, `"custom_key"` (`"header"`, `"cookie"`, `"host"`, `"query_string"`, `"user"`).

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "cache_key": {
    "ignore_query_strings_order": true,
    "cache_deception_armor": true,
    "custom_key": {
      "query_string": {
        "include": [
          "*"
        ]
      },
      "header": {
        "include": [
          "header1"
        ],
        "check_presence": [
          "header_1"
        ],
        "contains": {
          "accept-encoding": ["br", "zstd"]
        }
      },
      "cookie": {
        "include": [
          "cookieName1"
        ],
        "check_presence": [
          "cookie_1"
        ]
      },
      "user": {
        "device_type": true,
        "geo": true,
        "lang": true
      },
      "host": {
        "resolved": false
      }
    }
  }
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Cache Reserve Eligibility

Cache Reserve eligibility allows you to specify which website resources should be eligible for our persistent cache called [Cache Reserve](/cache/advanced-configuration/cache-reserve/). If the request matches and also meets [eligibility criteria](/cache/advanced-configuration/cache-reserve/#cache-reserve-asset-eligibility), Cloudflare will write the resource to cache reserve. This requires an add-on cache reserve plan.

This rule can also be used to specify Cache Reserve eligibility for website resources based on their size. For example, by specifying that all assets which are eligible be 100 MB and above, Cloudflare will look for eligible assets at or above 100 MB for Cache Reserve eligibility and only persistently store those assets.

:::note

Cloudflare will still enforce the plan-based [cacheable file limits](/cache/concepts/default-cache-behavior/#customization-options-and-limits) when using this configuration.
:::

<Details header="API information">

API configuration object name: `"cache_reserve"`.

API property name for enabling Cache Reserve: `"eligible"` (boolean).

```json title="API configuration example"
"action_parameters": {
  "cache": true
  "cache_reserve": {
    "eligible": true,
    "minimum_file_size": 100000
  }
}
```

:::note
If `minimum_file_size` is omitted and `eligible` is true, Cloudflare will use 0 bytes by default.
:::

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Caching on Port (Enterprise-only)

Cloudflare supports several [network ports](/fundamentals/reference/network-ports/#network-ports-compatible-with-cloudflares-proxy) by default, like 80 or 443. Some ports, traditionally admin ports, are supported but have caching disabled as they are used to manage sensitive information that should be ineligible for cache. Enterprise customers wanting to enable caching on these admin ports can cache on these ports by entering their desired port.

:::note

Cloudflare supports many ports by default and will cache on them without needing this rule to be configured. For ports that Cloudflare supports, but for which caching is disabled, use this rule.
:::

<Details header="API information">

API configuration property name: `"additional_cacheable_ports"` (array of integer values).

```json title="API configuration example"
"action_parameters": {
    "cache": true
    "additional_cacheable_ports": [8443, 8080]
  }
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Proxy Read Timeout (Enterprise-only)

Defines a timeout value between two successive read operations to your origin server. The default value can be found in the [Connection limits](/fundamentals/reference/connection-limits/) table. If you are attempting to reduce [`HTTP 524`](/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-524/) errors because of timeouts from an origin server, try increasing this timeout value using the API endpoint below.

<Details header="API information">

API configuration property name: `"read_timeout"` (integer).

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "read_timeout": 900
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Serve stale content while revalidating

Defines if Cloudflare will serve stale content while updating the latest content from the origin server. If serving stale content is disabled, Cloudflare will not serve stale content while getting the latest content from the origin.

<Details header="API information">

API configuration property name: `"serve_stale"` > `"disable_stale_while_updating"` (boolean).

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "serve_stale": {
    "disable_stale_while_updating": true
  }
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Origin range requests

Origin Range Requests let Cloudflare fetch large files from your origin in cache-aligned byte ranges. Cloudflare may expand a client range and issue several single-range origin requests. Your origin may receive ranges that differ from the client's `Range` header.

This setting does not make a request or response cacheable. Cloudflare can use origin range fetching for an eligible `GET` even if the response is not stored. File-size limits apply to the complete object, not the requested range. [Cache Reserve](/cache/advanced-configuration/cache-reserve/#limits) does not support Origin Range Requests.

Cloudflare asks the origin for unencoded content. Origins should return compatible range responses throughout the request. If an origin ignores `Range` and returns `200 OK`, Cloudflare can use the response but must download the complete file. For all origin and client response requirements, refer to [Range request behavior](/cache/reference/range-requests/).

<Details header="API information">

API configuration object name: `"origin_range_requests"`.

The `mode` property accepts these values:

| Value | Behavior |
| --- | --- |
| `on` | Allows Cloudflare to generate origin range requests for matching, eligible `GET` requests. |
| `off` | Does not generate Origin Range Requests. Cloudflare can still serve ranges from a complete cached file. |
| `default` | Applies no rule override. Cloudflare uses its platform default. No file extensions are currently turned on by default. |

The `mode` property is required. The `on` and `off` modes override the default behavior for matching requests.

The `off` mode controls only range requests generated by Origin Range Requests. On a cacheable miss, Cloudflare may remove the client's `Range` header and fetch the complete file. If caching is bypassed or the request is not eligible for caching, Cloudflare may forward the client's original `Range` header.

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "origin_range_requests": {
    "mode": "on"
  }
}
```

To override Cloudflare's default behavior, use `off` in a rule that matches the affected traffic.

```json title="Turn off default Origin Range Requests"
"action_parameters": {
  "origin_range_requests": {
    "mode": "off"
  }
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-turn-off-default-origin-range-requests) for a complete opt-out example.

</Details>

#### Respect Strong ETags

Turn on or off byte-for-byte equivalency checks between the Cloudflare cache and the origin server. When enabled, Cloudflare will use [strong ETag](/cache/reference/etag-headers/#strong-etags) header validation to ensure that resources in the Cloudflare cache and on the origin server are byte-for-byte identical. If disabled, Cloudflare converts ETag headers into [weak ETag](/cache/reference/etag-headers/#weak-etags) headers.

<Details header="API information">

API configuration property name: `"respect_strong_etags"` (boolean).

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "respect_strong_etags": true
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Origin error page pass-through

Turn on or off Cloudflare error pages generated from error HTTP status codes sent from the origin server. If enabled, this setting enables the use of error pages issued by the origin.

<Details header="API information">

API configuration property name: `"origin_error_page_passthru"` (boolean).

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "origin_error_page_passthru": true
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Origin Cache Control (Enterprise-only)

When this option is enabled, Cloudflare will aim to strictly adhere to [RFC 7234](https://datatracker.ietf.org/doc/html/rfc7234). Enterprise customers have the ability to select if Cloudflare will adhere to this behavior. Free, Pro, and Business customers have this option enabled by default and cannot disable it.

<Details header="API information">

API configuration property name: `"origin_cache_control"` (boolean).

```json title="API configuration example"
"action_parameters": {
  "cache": true
  "origin_cache_control": true
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples.

</Details>

#### Vary

The `Vary` response header lets your origin cache multiple versions of the same URL based on request headers. Use the `vary` object to configure how Cloudflare handles each header your origin lists in its `Vary` response. For how Vary affects cache keys and how normalization works, refer to [Vary](/cache/concepts/vary/).

The `vary` object supports these keys:

| Key       | Required | Description                                                                                         |
| --------- | -------- | --------------------------------------------------------------------------------------------------- |
| `default` | Yes      | Configuration for any header name in the origin `Vary` response that is not included in `headers`. |
| `headers` | No       | A map of lowercase request header names to configuration objects.                                  |

If the `vary` object is omitted, this Cache Rules Vary setting is turned off. Other Vary behavior, such as `Vary: *`, [Vary for images](/cache/advanced-configuration/vary-for-images/), and compression handling, is unaffected. If the `vary` object is present, `default` is required. An empty `vary` object is invalid.

Each header configuration object, and the `default` object, must include an `action` key set to one of `normalize`, `passthrough`, or `bypass`. Refer to [Actions](/cache/concepts/vary/#actions) for guidance on when to use each.

Additional parameters can be specified for certain header names:

| Header            | Additional key | Description                                                                                   |
| ----------------- | -------------- | --------------------------------------------------------------------------------------------- |
| `accept`          | `media_types`  | List of MIME types to include when normalizing the `Accept` header. Maximum 10 items.         |
| `accept-language` | `languages`    | List of languages to include when normalizing the `Accept-Language` header. Maximum 20 items. |

For most deployments, start with a restrictive `default` and explicit per-header configuration:

- Use `default` set to `bypass` to avoid caching variants for unexpected origin `Vary` headers.
- Add explicit `headers` entries for the headers you expect your origin to vary on.
- Use `normalize` for `accept`, `accept-language`, and `accept-encoding` unless your origin requires raw header values.
- Use `media_types` and `languages` allowlists when you know the exact variants your origin can serve.
- Use `passthrough` only when exact raw header values should select different cached versions.
- Use `bypass` for high-cardinality headers such as `user-agent`, cookies, or request headers with per-user values.

The following limits and validation rules apply:

- Header names in `headers` must be lowercase.
- Header names can contain letters, numbers, underscores, and hyphens.
- Header names cannot exceed 128 characters.
- Header names beginning with `cf-` or `cf_` are not allowed.
- Certain hop-by-hop or cache-control headers, such as `connection`, `host`, and `cache-control`, are not allowed.
- `headers` can contain up to 50 entries.
- `accept.media_types` can contain up to 10 entries.
- `accept-language.languages` can contain up to 20 entries.
- Values in `media_types` and `languages` must be non-empty printable ASCII.

<Details header="API information">

API configuration object name: `"vary"`.

The following example normalizes `accept` and `accept-language`, and bypasses cache for any other header in the origin `Vary` response:

```json title="API configuration example"
"action_parameters": {
  "cache": true,
  "vary": {
    "default": {
      "action": "bypass"
    },
    "headers": {
      "accept": {
        "action": "normalize",
        "media_types": ["text/html", "application/json"]
      },
      "accept-language": {
        "action": "normalize",
        "languages": ["en", "fr", "de"]
      }
    }
  }
}
```

Refer to [Create a cache rule via API](/cache/how-to/cache-rules/create-api/#example-requests) for complete API examples, or to the [Terraform example](/cache/how-to/cache-rules/terraform-example/).

</Details>

# Terraform example

import { Details, Render } from "~/components";

The following example defines a single cache rule for a zone using Terraform. The rule configures several cache settings and sets a custom cache key for incoming requests addressed at `example.net`.

<Details header="Terraform `cloudflare_ruleset` resource">

```tf
# Cache rule configuring cache settings and defining custom cache keys
resource "cloudflare_ruleset" "cache_rules_example" {
  zone_id     = "<ZONE_ID>"
  name        = "Set cache settings"
  description = "Set cache settings for incoming requests"
  kind        = "zone"
  phase       = "http_request_cache_settings"

  rules = [
    {
      ref         = "cache_settings_custom_cache_key"
      description = "Set cache settings and custom cache key for example.net"
      expression  = "(http.host eq \"example.net\")"
      action      = "set_cache_settings"

      action_parameters = {
        edge_ttl = {
          mode    = "override_origin"
          default = 60
          status_code_ttl = [
            {
              status_code = 200
              value       = 50
            },
            {
              status_code_range = {
                from = 201
                to   = 300
              }
              value = 30
            }
          ]
        }
        browser_ttl = {
          mode = "respect_origin"
        }
        serve_stale = {
          disable_stale_while_updating = true
        }
        respect_strong_etags = true
        cache_key = {
          ignore_query_strings_order = false
          cache_deception_armor      = true
          custom_key = {
            query_string = {
              exclude = {
                all = true
              }
            }
            header = {
              include        = ["habc", "hdef"]
              check_presence = ["habc_t", "hdef_t"]
              exclude_origin = true
            }
            cookie = {
              include        = ["cabc", "cdef"]
              check_presence = ["cabc_t", "cdef_t"]
            }
            user = {
              device_type = true
              geo         = false
            }
            host = {
              resolved = true
            }
          }
        }
        origin_error_page_passthru = false
      }
    }
  ]
}
```

<Render
	file="terraform-use-ref-field-with-location"
	product="rules"
/>

</Details>

The following example configures [Vary](/cache/concepts/vary/). It normalizes `Accept` and `Accept-Language`, and bypasses cache for any other header in the origin `Vary` response.

<Details header="Terraform example: Cache expected Vary responses">

```tf
variable "zone_id" {
  type = string
}

resource "cloudflare_ruleset" "cache_vary_example" {
  zone_id     = var.zone_id
  name        = "Set cache settings"
  description = "Set cache settings for incoming requests"
  kind        = "zone"
  phase       = "http_request_cache_settings"

  rules = [
    {
      ref         = "cache_vary"
      description = "Cache expected Vary responses"
      expression  = "(http.host eq \"example.com\")"
      action      = "set_cache_settings"

      action_parameters = {
        cache = true

        vary = {
          default = {
            action = "bypass"
          }
          headers = {
            "accept" = {
              action = "normalize"
              media_types = [
                "text/html",
                "application/json"
              ]
            }
            "accept-language" = {
              action = "normalize"
              languages = [
                "en",
                "fr",
                "de"
              ]
            }
          }
        }
      }
    }
  ]
}
```

<Render
	file="terraform-use-ref-field-with-location"
	product="rules"
/>

</Details>

For additional guidance on using Terraform with Cloudflare, refer to [Terraform](/terraform/).

# Browser Cache TTL

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to adjust browser cache TTL for caching resources in the browser to one day for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache
  - **Browser TTL**: Override origin and use this TTL
  - **Input time-to-live (TTL)**: _1 day_

</Example>

# Bypass Cache on Cookie

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to bypass cache for requests containing cookie `test_cookie` for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com" AND Cookie contains "test-cookie"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com" and http.cookie contains "test-cookie")`

- **Then**:
  - **Cache eligibility**: Bypass cache

</Example>

# Cache everything for hostnames in a list

import { Example } from "~/components"

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to cache everything for hostnames that match a [custom hostname list](/waf/tools/lists/custom-lists/#lists-with-hostnames):

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname is in list "my_hostnames"`
  - Using the Expression Editor:<br/>
    `(http.host in $my_hostnames)`

- **Then**:
  - **Cache eligibility**: Eligible for cache

</Example>

:::note

The **is in list** operator requires an Enterprise plan. You must first [create a hostname list](/waf/tools/lists/create-dashboard/) in your account before you can reference it in a cache rule expression.
:::

# Cache Deception Armor

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to protect against cache deception attacks for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache
  - **Setting**: Cache key
    - **Cache deception armor**: On

</Example>

# Cache by Device Type

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to cache content based on user agent or device type for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache
  - **Setting**: Cache key
    - **Cache by device type**: On

</Example>

# Cache Everything while ignoring query strings

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to adjust cache level for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache
  - **Setting**: Cache key
    - **Query string**: Ignore query string

</Example>

# Cache Level (Cache Everything)

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to adjust cache level for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache

</Example>

:::caution

This option caches all HTML regardless of the presence of dynamic content. If you use this approach to cache pages containing dynamic content, visitors may receive information not intended for them. To avoid caching dynamic content, you can add a condition to the rule's matching criteria to prevent it from matching that content. Some examples include:

- Checking for the presence of a cookie.
- Negative matching against known dynamic content file paths.
- Negative matching against dynamic content extensions (or lack of an extension).
  :::

# Cache TTL by status code

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to cache responses with status code between `200` and `599` for one day for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache
  - **Setting**: Edge TTL
    - Use cache-control header if present, use default Cloudflare caching behavior if not
    - **Status code TTL**:
      - **Scope**: _Range_
      - **From**: _200_
      - **To**: _599_
      - **Duration**: _1 day_

</Example>

# Custom Cache Key

import { Example, Render } from "~/components";

<Render file="page-rules-migration" product="cache" />

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to set a custom cache key for all query string parameters, for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache
  - **Setting**: Cache key
    - **Query string**: All query string parameters

</Example>

Refer to [cache keys](/cache/how-to/cache-keys/) for more information on possible settings when configuring a custom cache key.

# Edge Cache TTL

import { Example } from "~/components";

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to adjust edge cache TTL for caching resources on Cloudflare edge to one day, for any hostname containing `example.com`:

<Example>

- **When incoming requests match**: Custom filter expression
  - Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  - Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

- **Then**:
  - **Cache eligibility**: Eligible for cache
  - **Setting**: Edge TTL
    - Ignore cache-control header and use this TTL
      - **Input time-to-live (TTL)**: _1 day_

</Example>

# Examples

import { DirectoryListing } from "~/components"

In the following sections, you can find common cache rules examples.

<DirectoryListing />

# Origin Cache Control

import { Example } from "~/components"

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to determine edge cache behavior for any hostname containing `example.com`:

<Example>

* **When incoming requests match**: Custom filter expression
  * Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  * Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

* **Then**:
  * **Cache eligibility**: Eligible for cache
  * **Setting**: Origin Cache Control
    * **Enable Origin Cache Control**: Off

</Example>

# Query String Sort

import { Example } from "~/components"

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to sort query string parameters for caching purposes, for any hostname containing `example.com`:

<Example>

* **When incoming requests match**: Custom filter expression
  * Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  * Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

* **Then**:
  * **Cache eligibility**: Eligible for cache
  * **Setting**: Cache key
    * **Sort query string**: On

</Example>

# Respect Strong ETags

import { Example } from "~/components"

[Create a cache rule](/cache/how-to/cache-rules/create-dashboard/) to respect strong ETags for any hostname containing `example.com`:

<Example>

* **When incoming requests match**: Custom filter expression
  * Using the Expression Builder:<br/>
    `Hostname contains "example.com"`
  * Using the Expression Editor:<br/>
    `(http.host contains "example.com")`

* **Then**:
  * **Cache eligibility**: Eligible for cache
  * **Setting**: Respect strong ETags
    * **Use strong ETag headers**: On

</Example>

# Edge and Browser Cache TTL

Edge Cache TTL (Time to Live) specifies the maximum time to cache a resource in the Cloudflare global network. Edge Cache TTL is not visible in response headers and the minimum Edge Cache TTL depends on plan type.

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |
| Minimum Edge Cache TTL | 2 hours | 1 hour | 1 second | 1 second |

For more information on how to set up Edge Cache TTL, refer to [Cache rules](/cache/how-to/cache-rules/settings/#edge-ttl).

## Browser Cache TTL

The Browser Cache TTL sets the expiration for resources cached in a visitor’s browser. By default, Cloudflare honors the cache expiration set in your `Expires` and `Cache-Control` headers but overrides those headers if:

* The value of the `Expires` or `Cache-Control` header from the origin web server is less than the Browser Cache TTL Cloudflare setting.
* The origin web server does not send a `Cache-Control` or an `Expires` header.

Unless specifically set in a cache rule, Cloudflare does not override or insert `Cache-Control` headers if you set **Browser Cache TTL** to **Respect Existing Headers**.

:::note[Note]


* Setting high Browser Cache TTL values means that the assets will be cached for a long time by users’ browsers.
* If you modify cached assets, the new assets may not be displayed to repeat visitors before the Browser Cache TTL expires.
* Purging Cloudflare’s cache does not affect assets stored by a visitor’s browser. 
  :::

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |
| Minimum Browser Cache TTL (Page Rules) | 2 minutes | 2 minutes | 2 minutes | 30 seconds |
| Minimum Browser Cache TTL | 1 second | 1 second | 1 second | 1 second |
| Default Browser Cache TTL | 4 hours | 4 hours | 4 hours | 4 hours |

For more information on setting the Browser Cache TTL, refer to [Set Browser Cache TTL](/cache/how-to/edge-browser-cache-ttl/set-browser-ttl/).

# Set Browser Cache TTL

import { DashButton } from "~/components";

Specify a time for a visitor’s Browser Cache TTL to accelerate the page load for repeat visitors to your website. To configure cache duration within Cloudflare’s data centers, refer to [Edge Cache TTL](/cache/how-to/cache-rules/settings/#edge-ttl).

By default, Cloudflare honors the cache expiration set in your `Expires` and `Cache-Control` headers. Cloudflare overrides any `Cache-Control` or `Expires` headers with values set via the **Browser Cache TTL** option under **Caching** on your dashboard if:

* The value of the `Cache-Control` header from the origin web server is less than the **Browser Cache TTL** setting. This means that **Browser cache TTL** value needs to be higher than origin `max-age`.
* The origin web server does not send a `Cache-Control` or an `Expires` header.

Unless specifically set in a [Cache Rule](/cache/how-to/cache-rules/), Cloudflare does not override or insert `Cache-Control` headers if you set **Browser Cache TTL** to **Respect Existing Headers**.

Nevertheless, the value you set via Cache Rule will be ignored if `Cache-Control: max-age` is higher. In other words, you can override to make browsers cache longer than Cloudflare's edge but not less.

## Set Browser Cache TTL

:::note[Note]


If you modify cached assets, the new asset is not displayed to repeat visitors before the Browser Cache TTL duration. [Purging Cloudflare’s cache](/cache/how-to/purge-cache/) does not affect assets cached in a visitor’s browser.


:::

1. In the Cloudflare dashboard, go to the **Caching** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Under **Browser Cache TTL**, select the desired cache expiration time from the drop-down menu.

The **Respect Existing Headers** option tells Cloudflare to honor the settings in the `Cache-Control` headers from your origin web server.

# Purge cache

import { DirectoryListing } from "~/components";
export const limitsDetails =
	"If your account includes zones with different Cloudflare plans, the above limits are shared between all the zones with the same plan. For example, all the zones in your account with a Pro plan will share the limits for the Pro plan, and all the zones in your account with a Business plan will share the limits for the Business plan.";

Cloudflare's Instant Purge ensures that updates to your content are reflected immediately. Multiple options are available for purging content, with single-file cache purging (purge by URL) being the recommended method. However, the following additional options are also available:

<DirectoryListing />

To purge cached content using the Cloudflare API, refer to [Purge Cached Content](/api/resources/cache/methods/purge/).

To mark content as stale instead of removing it, [invalidate cached content](/cache/guides/invalidate-cache/). Cloudflare revalidates invalidated content on the next request and reuses content that has not changed.

:::note
A successful purge request returns `HTTP 200`. This indicates that Cloudflare received the request — it does not confirm that Cloudflare cached the targeted content or evicted any content. To verify a purge, request the asset after purging and confirm that [`CF-Cache-Status`](/cache/concepts/cache-responses/) is no longer `HIT`.
:::

:::note
If versioning is active on your zone and multiple environments are configured, you can select the specific environment you want to purge. For more details, refer to the [Version Management](/version-management/) documentation.
:::

## Availability and limits

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | Yes | Yes | Yes | Yes |
| Purge options | URL, Hostname, Tag, Prefix, and Purge Everything | URL, Hostname, Tag, Prefix, and Purge Everything | URL, Hostname, Tag, Prefix, and Purge Everything | URL, Hostname, Tag, Prefix, and Purge Everything |

Invalidation requests count toward the same limits as purge requests. For details, refer to [Invalidation limits](/cache/guides/invalidate-cache/#limits).

### Hostname, tag, prefix URL, and purge everything limits

The following limits apply per **account** to purge and invalidation requests:

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Requests | 5 requests per minute | 5 requests per second | 10 requests per second | 50 requests per second |
| Bucket size | 25 | 25 | 50 | 500 |
| Max operations per request | 100 | 100 | 100 | 100 |

<p>{limitsDetails}</p>

### Single-file purge limits

The following limits apply per **account** to purge and invalidation requests:

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| URLs | 800 URLs per second | 1500 URLs per second | 1500 URLs per second | 3000 URLs per second |
| Max operations per request | 100 | 100 | 100 | 500 |

<p>{limitsDetails}</p>

Note that the thresholds for URLs are calculated using a moving average.

### Token bucket rate limiting

Cloudflare uses token bucket rate limiting to limit the number of purge requests flowing through the system at any given time, ensuring a steady and manageable flow.

Each account tier has a defined request rate (for example, Free: 5 requests per minute, Business: 10 requests per second), and requests are only allowed if there are available tokens in the bucket. Tokens refill at a consistent rate, but each bucket has a maximum capacity (for example, Free: 25 tokens, Enterprise: 500 tokens), allowing short bursts of requests if tokens have accumulated.

If the bucket is empty, further requests must wait until new tokens are added. This system maintains fair usage while allowing occasional bursts within the bucket's capacity.

If you are an Enterprise customer and you need more operations, reach out to your account team for support.

# Purge cache by hostname

import { DashButton } from "~/components"

Purging by hostname means that all assets at URLs with a host that matches one of the provided values will be instantly purged from the cache.

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Under **Purge Cache**, select **Custom Purge**. The **Custom Purge** window appears.
3. Under **Purge by**, select **Hostname**.
4. Follow the syntax instructions:
   * One hostname per line.
   * Separated by commas.
   * You can purge up to 100 hostnames at a time.
5. Enter the appropriate value(s) in the text field using the format shown in the example.
6. Select **Purge**.

For information on rate limits, refer to the [Availability and limits](/cache/how-to/purge-cache/#availability-and-limits) section.

## Resulting cache status

Purging by hostname deletes the resource, resulting in the `CF-Cache-Status` header being set to [`MISS`](/cache/concepts/cache-responses/#miss) for subsequent requests.

If [tiered cache](/cache/how-to/tiered-cache/) is used, purging by hostname may return `EXPIRED`, as the lower tier tries to revalidate with the upper tier to reduce load on the latter.
Depending on whether the upper tier has the resource or not, and whether the end user is reaching the lower tier or the upper tier, `EXPIRED` or `MISS` are returned.

# Purge by single-file

import { DashButton } from "~/components"

With purge by single-file, cached resources are instantly removed from the stored assets in your Content Delivery Network (CDN) across all data centers. New requests for the purged asset receive the latest version from your origin web server and add it back to your CDN cache within the specific Cloudflare data center that served the request.

For information on single-file purge rate limits, refer to the [limits](/cache/how-to/purge-cache/#single-file-purge-limits) section.

## How to purge a single file

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Under **Purge Cache**, select **Custom Purge**. The **Custom Purge** window appears.
3. Under **Purge by**, select **URL**.
4. Enter the appropriate value(s) in the text field using the format shown in the example. Be aware that the host part of the URL is not case-sensitive, meaning it will always be converted to lowercase according to RFC standards. However, the path portion is case-sensitive. For example, `https://EXAMPLE.com/helloHI` would be treated as `https://example.com/helloHI`.
5. Perform any additional instructions to complete the form.
6. Review your entries.
7. Select **Purge**.

## Limitations and alternatives

Single-file purge works for most resources, but there are situations where it cannot clear cached content. This section explains when single-file purge does not work and what to use instead.

### Custom cache keys

If you use [Cache Rules](/cache/how-to/cache-rules/) to set a [custom cache key](/cache/how-to/cache-keys/) that includes headers, cookies, or other request properties, single-file purge via the dashboard will not remove the cached resource. This is because the dashboard cannot send those values in a purge request. The exception is device type and country, which Enterprise zones can set under **Advanced (Purging with custom cache keys)** when purging by URL. Custom cache keys that only change how the query string is handled (for example, ignoring the query string) generally work with dashboard single-file purge.

**What to do instead:**

- **Use the API** to [purge files by URL](/api/resources/cache/methods/purge/#purge-cached-content-by-url), including all headers and cookies that are part of your custom cache key. If any header or cookie is missing from the purge request, Cloudflare treats it as an empty value in the cache key.
- **Use purge by prefix** ([purge by prefix](/cache/how-to/purge-cache/purge_by_prefix/)) to clear all resources under a URL path.
- **Use purge by tag** ([purge by tag](/cache/how-to/purge-cache/purge-by-tags/)) if your resources are tagged.
- **Use purge everything** ([purge everything](/cache/how-to/purge-cache/purge-everything/)) to clear all cached resources for the zone.

### Cache Rules that match on request properties

Single-file purge may also not work as expected if your Cache Rules match only on `GET` requests, or match on properties that are not present during a purge. For example, a Cache Rule with the expression `(http.host eq "example.com" and http.request.method eq "GET")` will not match during a single-file purge.

**What to do instead:**

Update your Cache Rule expression to also match on the `PURGE` method, for example `(http.host eq "example.com" and (http.request.method eq "GET" or http.request.method eq "PURGE"))`. This allows the rule to apply to both client requests and purge requests.

For rules that match on fields which cannot be evaluated during purge (such as `cf.bot_management.score`), use [purge by prefix](/cache/how-to/purge-cache/purge_by_prefix/), [purge by tag](/cache/how-to/purge-cache/purge-by-tags/), or [purge everything](/cache/how-to/purge-cache/purge-everything/).

### Redirect responses

If the URL you want to purge returns a redirect (`301` or `302`), single-file purge removes the cached redirect response — not the content at the redirect destination. The resource at the destination URL remains cached.

To clear the destination content, purge the final destination URL directly. You can find it by following the redirect chain to its end:

```bash
curl -Ls -o /dev/null -w "%{url_effective}
" https://example.com/redirecting-path
```

This outputs the final URL after following all redirects. Use that URL for your purge request.

### Resources with special headers

A single-file purge performed through your Cloudflare dashboard does not clear objects that contain any of the following:

- [Origin header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin)
- Any of these request headers:
  - `X-Forwarded-Host`
  - `X-Host`
  - `X-Forwarded-Scheme`
  - `X-Original-URL`
  - `X-Rewrite-URL`
  - `Forwarded`

You can purge objects with these characteristics using an API call to [purge files by URL](/api/resources/cache/methods/purge/). In the `headers` object of the request body, include the header values that match those used in the cached resource's cache key.

## Additional notes

:::caution
Always use UTF-8 encoded URLs for single-file cache purges. Wildcards are not supported on single file purge, and you must use purge by hostname, prefix, or implement cache tags as an alternative solution.
:::

:::caution
If you have a [Transform Rule](/rules/transform/) in place that is modifying part of a URL path, you must use the non-transform (end user) URL when performing single file purge so that purge can take effect.
:::

:::note
For information on how to use single-file purge to purge assets cached by a Workers fetch, refer to [Single file purge assets cached by a Worker](/workers/reference/how-the-cache-works/#single-file-purge-assets-cached-by-a-worker).

For information on how to purge assets cached by [Cache API](/workers/runtime-apis/cache/) operations, refer to [Purge assets stored with the Cache API](/workers/reference/how-the-cache-works/#purge-assets-stored-with-the-cache-api).
:::

## Resulting cache status

Purging by single-file deletes the resource, resulting in the `CF-Cache-Status` header being set to [`MISS`](/cache/concepts/cache-responses/#miss) for subsequent requests.

# Purge cache by cache-tags

import { DashButton } from "~/components"

Cache-tag purging makes multi-file purging easier because you can instantly bulk purge by adding cache-tags to your assets, such as webpages, image files, and more.

## General workflow for cache-tags

1. Add tags to the `Cache-Tag` HTTP response header from your origin web server for your web content, such as pages, static assets, etc.
2. [Ensure your web traffic is proxied](/dns/proxy-status/) through Cloudflare.
3. Cloudflare associates the tags in the `Cache-Tag` HTTP header with the content being cached.
4. Use specific cache-tags to instantly purge your Cloudflare CDN cache of all content containing that cache-tag from your dashboard or [using our API](/api/resources/cache/methods/purge/).
5. Cloudflare forces a [cache MISS](/cache/concepts/cache-responses/#miss) on content with the purged cache-tag.

:::caution[Warning]


Be careful when purging. A cache MISS can cause execution delays by requiring a fetch from your origin server.


:::

## Add Cache-Tag HTTP response headers

You add cache-tags to your web content in `Cache-Tag` HTTP response headers to allow the client and server to pass additional information in requests or responses. HTTP headers consist of a specific case-insensitive name followed by a colon `:` and the valid value, for example, `Cache-Tag:tag1,tag2,tag3`. Use commas to separate the tags when you want to use multiple cache-tags.

When your content reaches our edge network, Cloudflare:

* Removes the `Cache-Tag` HTTP header before sending the response to your website visitor or passing the response to a [Worker](/workers/). Your end users or Worker never see `Cache-Tag` HTTP headers on your Cloudflare-enabled website.
* Removes whitespaces from the header and any before and after cache-tag names: `tag1`, `tag2` and `tag1,tag2` are considered the same.
* Removes all repeated and trailing commas before applying cache-tags: `tag1,,,tag2` and `tag1,tag2` are considered the same.

## A few things to remember

* A single HTTP response can have more than one `Cache-Tag` HTTP header field.
* The minimum length of a cache-tag is one byte.
* Individual tags do not have a maximum length, but the aggregate `Cache-Tag` HTTP header cannot exceed 16 KB after the header field name, which is approximately 1,000 unique tags. Length includes whitespace and commas but does not include the header field name.
* For cache purges, the maximum length of a cache-tag in an API call is 1,024 characters.
* The `Cache-Tag` HTTP header must only contain printable ASCII encoded characters.
* Spaces are not allowed in cache-tags.
* Case is not sensitive. For example, `Tag1` and `tag1` are considered the same.

## Purge using cache-tags

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Under **Purge Cache**, select **Custom Purge**. The **Custom Purge** window appears.
3. Under **Purge by**, select **Tag**.
4. In the text box, enter your tags to use to purge the cached resources. To purge multiple cache-tagged resources, separate each tag with a comma or have one tag per line. You can purge up to 100 tags at a time.
5. Select **Purge**.

For information on rate limits, refer to the [Availability and limits](/cache/how-to/purge-cache/#availability-and-limits) section.


## Resulting cache status

Purging by tag deletes the resource, resulting in the `CF-Cache-Status` header being set to [`MISS`](/cache/concepts/cache-responses/#miss) for subsequent requests.

If [Tiered Cache](/cache/how-to/tiered-cache/) is used, purging by tag may return `EXPIRED`, as the lower tier tries to revalidate with the upper tier to reduce load on the latter.
Depending on whether the upper tier has the resource or not, and whether the end user is reaching the lower tier or the upper tier, `EXPIRED` or `MISS` are returned.

# Purge cache key resources

import { APIRequest } from "~/components";

Instantly purge resources that use Cache Keys via the [Cloudflare API](/api/resources/cache/methods/purge/). If you use [Cloudflare's Purge by URL](/api/resources/cache/methods/purge/#purge-cached-content-by-url), include the headers and query strings that are in your custom Cache Key.

Currently, it is not possible to purge a URL stored through Cache API that uses a custom cache key set by a Worker. Instead, use a [custom key created by Cache Rules](/cache/how-to/cache-rules/settings/#cache-key). Alternatively, purge your assets using purge everything, purge by tag, purge by host or purge by prefix.

To instantly purge by `device_type`, `geo`, or `lang` use `CF-Device-Type`, `CF-IPCountry` or `accept-language`, respectively. [Purge by Tag / Host](/api/resources/cache/methods/purge/#purge-cached-content-by-tag-host-or-prefix) and [Purge Everything](/api/resources/cache/methods/purge/#purge-all-cached-content) are not impacted by the use of custom Cache Keys.

## Purge by device type

For a Cache Key based on device type, purge the asset by passing the `CF-Device-Type` header with the API purge request (valid headers include mobile, desktop, and tablet).

Refer to the example API request below to instantly purge all mobile assets on the root webpage.

<APIRequest
  path="/zones/{zone_id}/purge_cache"
  method="POST"
  json={{
    files: [
      {
        url: "http://my.website.com/",
        headers: {
          "CF-Device-Type": "mobile"
        }
      }
    ]
  }}
/>

## Purge by geo

Instantly purge resources for a location-based Cache Key by specifying the two-letter country code. Spain is used in the example below.

<APIRequest
  path="/zones/{zone_id}/purge_cache"
  method="POST"
  json={{
    files: [
      {
        url: "http://my.website.com/",
        headers: {
          "CF-IPCountry": "ES"
        }
      }
    ]
  }}
/>

## Purge by language

For a Cache Key based on language, purge the asset by passing the `accept-language` header. Refer to the example API request below to instantly purge all assets in Chinese (PRC).

<APIRequest
  path="/zones/{zone_id}/purge_cache"
  method="POST"
  json={{
    files: [
      {
        url: "http://my.website.com/",
        headers: {
          "accept-language": "zh-CN"
        }
      }
    ]
  }}
/>

# Purge everything

import { DashButton } from "~/components"

To maintain optimal site performance, Cloudflare strongly recommends using single-file (by URL) purging instead of a complete cache purge.

Purging everything instantly clears all resources from your CDN cache in all Cloudflare data centers. Each new request for a purged resource is a cache miss. Cloudflare fetches the full resource from your origin server and caches it again.

To have Cloudflare revalidate cached resources with your origin server instead, [invalidate everything](/cache/guides/invalidate-cache/). If your origin server confirms that a resource has not changed, Cloudflare reuses the cached copy.

:::caution
When you purge everything, all cached content for your zone is removed at once. Every subsequent request must be served from your origin until the cache is repopulated. On high-traffic sites with many assets, this can cause a large spike in origin requests and may significantly slow down your site or overload your origin server.

Before using purge everything, consider whether a more targeted method — such as [purge by URL](/cache/how-to/purge-cache/purge-by-single-file/), [purge by prefix](/cache/how-to/purge-cache/purge_by_prefix/), or [purge by tag](/cache/how-to/purge-cache/purge-by-tags/) — can achieve the same result with less impact on your origin. If your zone uses [Tiered Cache](/cache/how-to/tiered-cache/), cache repopulation also propagates across cache tiers, which can further increase origin load during this period.
:::

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Under **Purge Cache**, select **Purge Everything**. A warning window appears.
3. If you agree, select **Purge Everything**.

:::note
When purging everything for a non-production cache environment, all files for that specific cache environment will be purged. However, when purging everything for the production environment, all files will be purged across all environments.
:::

For information on rate limits, refer to the [Availability and limits](/cache/how-to/purge-cache/#availability-and-limits) section.

## Resulting cache status

After you purge everything, the next request for each resource returns a `CF-Cache-Status` header of [`MISS`](/cache/concepts/cache-responses/#miss).

# P​urge varied images

Purging varied images instantly purges all content variants for that URL. This behavior occurs so that if an image changes, you can easily update the cache with a single purge request instead of trying to determine the potential number of out-of-date variants. The behavior is true regardless of [purge type](/cache/how-to/purge-cache/) used, such as [single file](/cache/how-to/purge-cache/purge-by-single-file/), [tag](/cache/how-to/purge-cache/purge-by-tags/), or [hostname](/cache/how-to/purge-cache/purge-by-hostname/).

# Purge zone versions via API

To purge zone versions via the Cloudflare API, follow these steps:

## Step 1: Retrieve the environment ID

First, retrieve your zone's environment ID by sending a request to the following API endpoint:

```bash
https://api.cloudflare.com/client/v4/zones/<zone_id>/environments
```

This API call will return a JSON response similar to the example below:

```json
{
  "result": {
    "environments": [
      {
        "name": "Production",
        "ref": "12abcd3e45f678940a573f51834a54",
        "version": 0,
        "expression": "(cf.zone.name eq \"example.com\")",
        "locked_on_deployment": false,
        "position": {
          "before": "5d41402abc4b2a76b9719d911017c"
        }
      },
      {
        "name": "Staging",
        "ref": "5d41402abc4b2a76b9719d911017c",
        "version": 0,
        "expression": "((cf.edge.server_ip in {1.2.3.4 5.6.7.8})) and (cf.zone.name eq \"example.com\")",
        "locked_on_deployment": false,
        "position": {
          "before": "49f0bad299687c62334182178bfd",
          "after": "12abcd3e45f678940a573f51834a54"
        }
      },
      {
        "name": "Development",
        "ref": "49f0bad299687c62334182178bfd",
        "version": 0,
        "expression": "((any(http.request.cookies[\"development\"][*] eq \"true\"))) and (cf.zone.name eq \"example.com\")",
        "locked_on_deployment": false,
        "position": {
          "after": "5d41402abc4b2a76b9719d911017c"
        }
      }
    ]
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

In this particular example, we have three environments: Production, Staging, and Development. You can find the environment ID in the `ref` field.

## Step 2: Purge cache per environment

To purge the Production environment, use the general cache purge endpoint:

```bash
https://api.cloudflare.com/client/v4/zones/<zone_id>/purge_cache/
```

To purge non-production environments, you must use a new `purge_cache` endpoint and specify the environment you would like to purge.

To purge the Staging environment from the example above, send a request to the following endpoint: 

```bash
https://api.cloudflare.com/client/v4/zones/<zone_id>/environments/5d41402abc4b2a76b9719d911017c/purge_cache/
```

To purge the Development environment from the example above, send a request to the following endpoint:

```bash
https://api.cloudflare.com/client/v4/zones/<zone_id>/environments/49f0bad299687c62334182178bfd/purge_cache/
```

:::note
When purging everything for a non-production cache environment, all files for that specific cache environment will be purged. However, when purging everything for the production environment, all files will be purged across all environments.
:::

# Purge cache by prefix (URL)

import { DashButton } from "~/components"

You can instantly purge their cache by URL prefix or path separators in their URL. For an example URL like `https://www.example.com/foo/bar/baz/qux.jpg`, valid purge requests include:

* `www.example.com`
* `www.example.com/foo`
* `www.example.com/foo/bar`
* `www.example.com/foo/bar/baz`
* `www.example.com/foo/bar/baz/qux.jpg`

Purging by prefix is useful in different scenarios, such as:

* Purging everything within a directory.
* Increasing control over cached objects in a path.
* Simplifying the number of purge calls sent.

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Under **Purge Cache**, select **Custom Purge**. The **Custom Purge** window appears.
3. Under **Purge by**, select **Prefix**.
4. Follow the syntax instructions.
   * One prefix per line.
   * Maximum 100 prefixes per request.
5. Enter the appropriate value(s) in the text field using the format shown in the example.
6. Select **Purge**.

For information on rate limits, refer to the [Availability and limits](/cache/how-to/purge-cache/#availability-and-limits) section.

:::caution[Warning]


If you have a [Transform Rule](/rules/transform/) in place that is modifying part of a URL path, you must use the post-transformed (origin) URL when performing a prefix purge so that purge can take effect.


:::

## Resulting cache status

Purging by prefix deletes the resource, causing `CF-Cache-Status` header to show [`MISS`](/cache/concepts/cache-responses/#miss) for the subsequent request.

If [tiered cache](/cache/how-to/tiered-cache/) is used, purging by prefix may return `EXPIRED`, as the lower tier tries to revalidate with the upper tier to reduce load on the latter.
Depending on whether the upper tier has the resource or not, and whether the end user is reaching the lower tier or the upper tier, `EXPIRED` or `MISS` are returned.

## Limitations

There are several limitations regarding purge by prefix:

* Path separators are limited to 31 for a prefix `(example.com/a/b/c/d/e/f/g/h/i/j/k/l/m…)`.
* Purge requests are limited to 100 prefixes per request.
* [Purge rate-limits apply](/api/resources/cache/methods/purge/).
* URI query strings & fragments cannot purge by prefix:
  * `www.example.com/foo?a=b` (query string)
  * `www.example.com/foo#bar` (fragment)

:::caution[Warning]


Because purge by prefix purges a directory, any URI for a resource within the purged directory is purged regardless of query string or fragment (though fragments are not generally sent by browsers). Purge by prefix rules do not accept fragments and query strings.

Example: If you purge `foo.com/bar`, any asset that starts with `foo.com/bar` will be purged, for example, `foo.com/bar/baz`, `foo.com/bar?good=bad`, etc. and purging `foo.com/bar?good=bad` itself will not work.


:::

## Purge by prefix normalization

Using purge by prefix normalization, when a purge by prefix request comes into Cloudflare for a normalized URL path, the purge service respects the [URL normalization](/rules/normalization/) and purges the normalized URL.

### How does URL Normalization work

Take the following website as an example: `https://cloudflare.com/انشاء-موقع-الكتروني/img_1.jpg`. The table below shows you how Cloudflare’s cache views these paths with [normalization on/off](/rules/normalization/).

<table>
  <tbody>
    <th colspan="5" rowspan="1">
      Request from visitor to EDGE
    </th>
    <th colspan="5" rowspan="1">
      What Cloudflare cache sees with Normalize Incoming URLs ON
    </th>
    <th colspan="5" rowspan="1">
      What Cloudflare cache sees with Normalize Incoming URLs OFF
    </th>
    <tr>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/انشاء-موقع-الكتروني/img_1.jpg</code>
      </td>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/%D8%A7%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A/img_1.jpg</code>
      </td>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/انشاء-موقع-الكتروني/img_1.jpg</code>
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/%D8%A7%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A/img_1.jpg</code>
      </td>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/%D8%A7%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A/img_1.jpg</code>
      </td>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/%D8%A7%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A/img_1.jpg</code>
      </td>
    </tr>
    <tr>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/hello/img_1.jpg</code>
      </td>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/hello/img_1.jpg</code>
      </td>
      <td colspan="5" rowspan="1">
        <code>https://cloudflare.com/hello/img_1.jpg</code>
      </td>
    </tr>
  </tbody>
</table>

As shown above, with URL normalization **ON**, visitors to the two URLs, `https://cloudflare.com/%D8%A7%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A/img_1.jpg` and `https://cloudflare.com/انشاء-موقع-الكتروني/img_1.jpg`, will be served the same cached asset. Purging `https://cloudflare.com/%D8%A7%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9-%D8%A7%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A/img_1.jpg` will instantly purge that asset for both visitors.

# Interaction with Cloudflare products

import { DirectoryListing } from "~/components"

Cloudflare caches content at data centers close to your visitors so your origin server handles fewer requests. The pages below explain how caching works when you also use Workers, R2 storage, or WAF security rules, and what to watch out for when combining them.

<DirectoryListing />

# Enable cache in an R2 bucket

By default, R2 buckets are private. To serve R2 objects through Cloudflare's cache, you need to attach a domain name you control — called a [Custom Domain](/r2/buckets/public-buckets/#custom-domains) — to the bucket. This creates a public URL backed by Cloudflare's network, so requests for your stored files go through Cloudflare's cache instead of hitting R2 directly every time.

Follow these steps to set up a Custom Domain for your bucket:

1. Go to **R2** and select your bucket.
2. On the bucket page, select **Settings**.
3. Under **Public access** > **Custom Domains**, select **Connect Domain**.
4. Enter the domain name you want to connect to and select **Continue**.
5. Review the new DNS record that will be added and select **Connect Domain**.

Cloudflare automatically adds a CNAME record that maps your domain to the bucket, generating a publicly available URL in the format `[name].domain.com`.

:::note
The development URL (`r2.dev`) does not support caching, WAF, or bot management. You must use a Custom Domain for these features.
:::

## Tiered Cache

By default, Cloudflare caches R2 content at edge data centers (the data centers closest to visitors) based on [cache rules](/cache/how-to/cache-rules/). Each edge data center that gets a cache miss fetches content directly from R2.

[Tiered Cache](/cache/how-to/tiered-cache/) changes this by organizing data centers into a hierarchy. When a nearby data center has a cache miss, it first checks a designated upper-tier data center before going to R2. This reduces the number of requests that reach R2.

To enable Tiered Cache for R2, configure [Smart Tiered Cache](/cache/how-to/tiered-cache/#smart-tiered-cache), which automatically selects the upper-tier data center with the lowest latency to your R2 bucket.

## Additional considerations

- **Access controls**: When you connect a Custom Domain, your R2 bucket becomes publicly accessible. Apply access controls to restrict who can request your files. Refer to [Control cache access with WAF and Snippets](/cache/interaction-cloudflare-products/waf-snippets/) for more information.
- **Cacheable size limits**: Files that exceed the [cacheable size limits](/cache/concepts/default-cache-behavior/#cacheable-size-limits) are not cached. Free, Pro, and Business plans have a limit of 512 MB per file. Enterprise plans default to 5 GB per file.
- **Default cached file types**: Cloudflare does not cache all file types by default. For example, HTML and JSON are not cached unless you create a [Cache Rule](/cache/how-to/cache-rules/) with the appropriate settings.

# Control cache access with WAF and Snippets

When you make an R2 bucket publicly accessible for caching (via a [Custom Domain](/r2/buckets/public-buckets/#custom-domains)), anyone who knows the URL can access the content. To restrict access, you can use Cloudflare's [WAF](/waf/custom-rules/use-cases/configure-token-authentication/) to validate requests before they reach the cache or your bucket.

The following diagram illustrates the flow of a request through WAF, Cache, and R2. WAF custom rules run before cache rules in the [request pipeline](/ruleset-engine/reference/phases-list/), so invalid requests are blocked before consuming cache resources.

```mermaid
flowchart LR
accTitle: Connections with Cloudflare
A[User's request] --> B[WAF] --> C[Cache] --> D[R2]
```

<br/>

## Presigned URLs

A presigned URL is a regular URL with a cryptographic token appended to it. The token contains a hash-based message authentication code (HMAC) computed from the URL path, a timestamp, and a secret key shared between the signing service and the validator. Anyone with the URL can access the content until the token expires, but the token cannot be reused for a different URL path.

You can presign URLs similar to [S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html), enabling you to share direct access to your content with an associated timeout. This approach can be implemented using a combination of Snippets, Rules, or Cloudflare Workers.

For optimal performance, we recommend separating the creation and validation processes:

- [Snippets](/rules/snippets/examples/signing-requests/) for HMAC creation (signing the URL)
- [WAF custom rules](/ruleset-engine/rules-language/functions/#hmac-validation) for HMAC validation (verifying the token on each request)

In the Workers documentation, the [Signing requests](/workers/examples/signing-requests/) example shows how to both generate and verify signed requests using HMAC. The Workers implementation is compatible with the WAF's [`is_timed_hmac_valid_v0()` validation function](/waf/custom-rules/use-cases/configure-token-authentication/), so you can sign with Workers and validate with WAF custom rules, or handle both in Workers.

# How Workers interact with Cache Rules

When you use both [Cache Rules](/cache/how-to/cache-rules/) and [Workers](/workers/) on the same request, the Worker's cache settings take priority — but only when the required [compatibility flags](#compatibility-flags) are enabled.

Your Workers script can override Cache Rules behavior, whether the request is for a domain proxied through Cloudflare or a domain that is not. For example, if a Cache Rule is configured to bypass cache for `example.com/foo`, but your Workers script sets `cacheEverything: true` in the [`cf` object](/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties) of a `fetch()` request, the Worker's setting takes precedence and the response is cached.

## Precedence order

Cache behavior is determined by the following order of precedence:

1. [Workers](/workers/) script settings
2. [Cache rules](/cache/how-to/cache-rules/)
3. [Page rules](/rules/page-rules/)

Workers override Cache Rules, and Cache Rules override Page Rules. When multiple rules at the same level match the same request, the [last matching rule wins](/cache/how-to/cache-rules/order/) for any conflicting settings.

## Compatibility flags

The override behavior is controlled by [compatibility flags](/workers/configuration/compatibility-flags/) — configuration settings that opt your Worker into specific runtime behaviors. There are two flags because Workers have two ways to interact with the cache:

- For the [Fetch API](/workers/runtime-apis/fetch/) (`fetch()` with `cf` properties): `request_cf_overrides_cache_rules`
- For the [Cache API](/workers/runtime-apis/cache/) (`caches.default.put()` / `caches.default.match()`): `cache_api_request_cf_overrides_cache_rules`

These flags must be enabled to allow Workers scripts to override Cache Rules. If the correct flag is not enabled for the API you are using, your Worker's cache settings are silently ignored and Cache Rules apply instead.

### Compatibility date behavior

A Worker's [compatibility date](/workers/configuration/compatibility-dates/) determines which flags are active by default. When you set a compatibility date, all flags with an enable date on or before that date are automatically turned on.

| Flag | Enabled by default | Prerequisite |
| --- | --- | --- |
| `request_cf_overrides_cache_rules` (Fetch API) | Compatibility dates on or after `2025-04-02` | None |
| `cache_api_compat_flags` | Compatibility dates on or after `2025-04-19` | None |
| `cache_api_request_cf_overrides_cache_rules` (Cache API) | Compatibility dates on or after `2025-05-19` | Requires `cache_api_compat_flags` |

The Cache API has an extra requirement: `cache_api_compat_flags` must be enabled for any compatibility flags to take effect on the Cache API. Without it, the Cache API ignores all compatibility flags, even ones you explicitly list in your configuration.

If your Worker uses a compatibility date before the dates listed above, you must manually add the flags to your configuration. Otherwise, cache behavior follows Cache Rules instead of the Worker's settings.

### Example (Older compatibility date)

A Cache Rule bypasses cache for `example.com/foo`. A Worker with a compatibility date before `2025-04-02` sets `cacheEverything: true` via `fetch()`. Because the compatibility date is too old for `request_cf_overrides_cache_rules` to be active by default, the Cache Rule wins and the response is not cached.

Similarly, if you use the Cache API and your compatibility date is before `2025-04-19`, `cache_api_compat_flags` is not active. Even if you manually add `cache_api_request_cf_overrides_cache_rules` to your configuration, it has no effect because the Cache API does not recognize compatibility flags without `cache_api_compat_flags`.

# Customize cache behavior with Workers

You can use [Workers](/workers/) to customize cache behavior on Cloudflare's network. Workers run as middleware in the request lifecycle — a single Worker handles both the request and response phases. When a request arrives, it hits the Worker before the cache is checked. The Worker can modify the incoming request (for example, rewrite the URL or add headers), then call `fetch()` to continue the request through the cache. When the response comes back — whether from cache or from the origin server — the Worker can also modify the response before it is sent to the visitor.

:::note
This page describes how Workers interact with a zone's Cloudflare Cache. You can also enable **[Workers Cache](/workers/cache/)** for your Worker — a cache that sits in front of the Worker itself, so that Cloudflare returns a cached response without running the Worker. Use Workers Caching to cache the output of a Worker's logic directly, independent of any zone configuration.
:::

The diagram below illustrates a common interaction flow between Workers and Cache.

![Workers and cache flow example flow diagram.](~/assets/images/cache/workers-cache-flow.png)

1. A visitor (a) requests a URL, and this request is directed to a Worker. The Worker can then interact with the request, either requesting the content from the origin server using (b) `fetch()` or sending a (f) response back to the visitor.
2. If the content is cached, the cache sends a (e) response back to the Worker, which can modify the response before sending a (f) response back to the visitor.
3. When using [cache rules](/cache/how-to/cache-rules/) with Workers, the cache rule must match the properties of the URL in the `fetch()` (b) request — such as headers, hostname, or URL path — not the original visitor URL/host (a). Otherwise, the rule will not be applied.

Here are a few examples of how Workers can be used to customize cache behavior:

- **Modify Response**: Adjust or enhance content after it is retrieved from the cache, ensuring that responses are up-to-date or tailored to specific needs.

- **Signed URLs**: Generate time-limited signed URLs to control access and enhance security.

- **Personalized Response**: Deliver personalized content based on user data while using cached resources to reduce the load on the origin server.

- **Reduce Latency**: Serve content from a data center close to the visitor, decreasing load times and improving the user experience.

You can also use [Snippets](/rules/snippets/) for lightweight modifications like header changes, redirects, and JWT validation without deploying a full Worker script. Snippets are included at no additional cost on all paid plans but have stricter resource limits (5 ms execution time, 32 KB package size).

:::note
When using Workers and [O2O](/cloudflare-for-platforms/cloudflare-for-saas/saas-customers/how-it-works/) (a traffic routing configuration where requests pass through two Cloudflare zones), some caveats and limitations may apply.
:::

## Cache features in Workers

Workers offer two ways to interact with the cache. Use `fetch()` when your Worker makes subrequests to an origin. Use the Cache API when your Worker generates responses without a backend origin.

- **fetch()**: When a Worker calls `fetch()`, the request passes through Cloudflare's cache and [Tiered Cache](/cache/how-to/tiered-cache/) (if enabled). You can control caching behavior by setting properties on the request's [`cf` object](/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties) — including time-to-live (TTL) values, custom cache keys, and cache headers. For more details, refer to [Cache using fetch](/workers/examples/cache-using-fetch/).

- **Cache API**: Allows you to programmatically store, retrieve, and delete responses in Cloudflare's cache using `caches.default` or `caches.open()`. Unlike `fetch()`, the Cache API only operates on the cache in the data center handling the current request — it does not interact with Tiered Cache. Use the Cache API when you need to cache responses that did not come from an origin. For more details, refer to [Using the Cache API](/workers/examples/cache-api/).

To understand more about how Cache and Workers interact, refer to [Cache in Workers](/workers/reference/how-the-cache-works/).

# Cache Analytics

import { DashButton } from "~/components"

Cache Analytics shows how much of your site's traffic is served from Cloudflare's cache versus your origin server. When content is served from cache, visitors get faster page loads and your origin web server handles less traffic. Use Cache Analytics to identify resources that are [missing from cache](/cache/concepts/cache-responses/#miss), [expired](/cache/concepts/cache-responses/#expired) (cached copy is outdated), or [ineligible for caching](/cache/concepts/cache-responses/#noneunknown) (not eligible for caching). You can filter by hostname, review the top URLs that miss cache, and query up to three days of data.

## Availability

|  | Free | Pro | Business | Enterprise |
| - | - | - | - | - |
| Availability | No | Yes | Yes | Yes |
| Retention period | N/A | 7 days | 30 days | 30 days |

## Access Cache Analytics

In the Cloudflare dashboard, go to the **Caching** page.

   <DashButton url="/?to=/:account/:zone/caching" />

## Requests vs Data Transfer

You can decide whether to focus on **Requests** or **Data Transfer**:

- **Requests** (default view) helps assess performance. Each cache [MISS](/cache/concepts/cache-responses/#miss) means the request must go to your origin server instead of being served from Cloudflare's cache, which adds latency.
- **Data Transfer** is useful for cost analysis, since most hosting providers charge for data sent from their servers (egress bandwidth).

You can switch between these views while keeping other analytics filters applied.

For best practices related to Cache Analytics, refer to [Cache performance](/cache/performance-review/cache-performance/).

## Add filters

Create filters to narrow the data to specific traffic segments. Example filters include **Cache status**, **Host**, **Path**, or **Content type**.

To add filters, under **Cache Performance**, select **Add filter**. Select **Apply** when you are done.

## Review cache status

The **Requests summary** graph shows how your traffic changes over time, such as in response to a high-traffic event or a recent configuration change. The Requests summary is based on a sample of requests, not the full dataset. Totals are extrapolated from the sample to represent overall traffic. For more information on how sampling works, refer to [Understanding sampling in Cloudflare Analytics](/analytics/sampling/).

**Served by Cloudflare** indicates content served by Cloudflare that did not require contacting your origin web server. **Served by Origin** indicates traffic served from the origin web server.

:::tip
In Cache Analytics, **Served by Cloudflare** reflects cache hits. For the broader definition — which also covers Workers responses, [URL forwarding](/rules/url-forwarding/) redirects, and `/cdn-cgi/` endpoints — refer to [Security Analytics — Request activity](/waf/analytics/security-analytics/#request-activity).
:::

Revalidated requests — where Cloudflare checks with your origin to confirm cached content is still current — are counted differently depending on the view. In the **Data Transfer** view, revalidated requests count as **Served by Cloudflare** because the response body is served from cache, not re-downloaded from the origin. In the **Requests** view, revalidated requests count as **Served by Origin** because Cloudflare still contacts the origin server to verify the content.

**Cache status** graphs break down why traffic is served from Cloudflare versus the origin web server, organized by content type.

For a breakdown of cache statuses and their descriptions, refer to [Cloudflare cache responses](/cache/concepts/cache-responses/).

## Review requests by source

Cache Analytics shows the most frequent values (top N) for several request attributes. Apply filters before reviewing these metrics to focus on specific traffic. For example, filtering to only view traffic with an Expired or Revalidated cache status shows which URLs were primarily responsible for those statuses.

### Empty content types

Finding an **empty** content type in your analytics is common. Responses to redirect status codes (`301`/`302`) typically do not include content, so they have no content type. Similarly, many HTTP error responses, such as `403`, do not return `text/html` and are also reported as empty.

# Cache performance

Your cache ratio measures how often Cloudflare serves content from cache instead of contacting your origin server. A higher ratio means faster responses for visitors and less traffic to your origin.

Depending on the [cache status](/cache/concepts/cache-responses/) you receive, you can make modifications to improve your cache ratio.

- **Dynamic**: The resource was not eligible for caching. This is the default response for many file types including HTML. To cache additional content, refer to [Cache Rules](/cache/how-to/cache-rules/).
- **Revalidated**: The resource was in cache but Cloudflare confirmed with your origin that it was still current before serving it. To address an atypical quantity of revalidated content, consider [increasing your Edge Cache TTLs](/cache/how-to/cache-rules/settings/#edge-ttl) (how long Cloudflare considers cached content fresh before checking your origin).
- **Expired**: The cached resource's TTL elapsed before it was requested again. Consider [extending Edge Cache TTLs](/cache/how-to/cache-rules/settings/#edge-ttl) for these resources via a Cache Rule, or configure your origin to return [revalidation headers](/cache/concepts/cache-control/) (`Last-Modified` or `ETag`) so Cloudflare can confirm content is still current without downloading it again.
- **Miss**: The resource was not found in cache and was served from your origin. Although tricky to optimize, there are a few potential remedies:
  - [Enable Tiered Cache](/cache/how-to/tiered-cache/#enable-tiered-cache) to check an upper-tier Cloudflare data center before contacting your origin server.
  - [Create a custom cache key](/cache/how-to/cache-rules/examples/custom-cache-key/) so that multiple URLs match the same cached resource, for example by ignoring the query string.

## Troubleshoot cache performance with example reports

Use [Cache Analytics](/cache/performance-review/cache-analytics/) to identify cache performance issues. The following examples show how to filter for common problems and resolve them.

- Not caching HTML.

  - Identify the issue: Select **Add filter** and select **Cache status equals Dynamic**.
  - Resolution: Set a Cloudflare Cache Rule to [cache dynamic content](/cache/how-to/cache-rules/examples/cache-everything/).

:::caution

This option caches all HTML regardless of the presence of dynamic content (content that changes per visitor, such as time of visit, location, or device). If you use this approach to cache pages containing dynamic content, visitors may receive information not intended for them. To avoid caching dynamic content, add a condition to the rule's matching criteria to exclude it. Some examples include:

- Checking for the presence of a cookie.
- Excluding requests that match known dynamic content file paths.
- Excluding requests with dynamic content extensions (or no extension).
:::

- Short cache expiration TTL.

  - Identify the issue: Select **Add filter** and select **Cache status equals Revalidated**.
  - Resolution: [Increase Cloudflare's Edge Cache TTL via a Cache Rule](/cache/how-to/cache-rules/examples/edge-ttl/).

- Need to enable Tiered Cache or custom cache key.

  - Identify the issue: Select **Add filter** and select **Cache status equals Miss**.
  - Resolution: [Enable Tiered Cache](/cache/how-to/tiered-cache/#enable-tiered-cache) or [create a custom cache key](/cache/how-to/cache-rules/examples/custom-cache-key/).

# Performance review

import { DirectoryListing } from "~/components"

Use Cache Analytics to understand how effectively Cloudflare caches your content, and apply optimization techniques to improve your cache hit ratio and reduce origin server load.

<DirectoryListing />

# CSAM Scanning Tool

The Child Sexual Abuse Material (CSAM) Scanning Tool allows website owners to proactively identify and take action on CSAM located on their website. By enabling this tool, Cloudflare will compare content served for your website through the Cloudflare cache to known lists of CSAM. These lists are provided to Cloudflare by leading child safety advocacy groups such as the National Center for Missing and Exploited Children (NCMEC).

Remember, by enabling the Service, you agree to the [Service-Specific Terms](https://www.cloudflare.com/service-specific-terms-application-services/#csam-scanning-tool-terms) for the CSAM Scanning Tool. You agree to use this tool solely for the purposes of preventing the spread of CSAM.

---

## Why would a URL be blocked?

Because knowingly distributing or viewing CSAM is illegal, the owner of the website has enabled Cloudflare's CSAM scanning tool to proactively identify and block images identified as CSAM located on their website.

---

## Configure the CSAM scanning tool

To enable the tool:

1. Log into the [Cloudflare dashboard](https://dash.cloudflare.com/).
2. Select your account and zone.
3. Go to **Caching** > **Configuration**.
4. For **CSAM Scanning Tool**, select **Configure**.

You must provide an email address, which will be used to notify you in the event Cloudflare detects a positive match.

---

## What happens when a match is detected?

When a potential match is detected with the tool:

1. An email is sent to you once per day to inform you of any detections made in the past 24 hours. This email will include the file paths of any content that was matched.
2. If possible, a block is placed to prevent further serving of the matched content. If a block fails, we will indicate that the content has not been blocked in the email.

---

## What action should I take when a match is detected?

You are responsible for understanding and complying with any legal obligations you have as a website owner when made aware of any potential CSAM. Although legal obligations vary based on the provider and the jurisdiction, website owners often have obligations to report apparent CSAM, to remove content, and to preserve records. Some of those possible obligations are as follows:

- You likely have an obligation to report apparent CSAM to the appropriate authorities. You can file a report to NCMEC with additional information via NCMEC's CyberTip reporting form or find the preferred reporting portal for your jurisdiction via the INHOPE website.

<br />

- You may need to preserve and securely store a copy of the content and related data in the case NCMEC or law enforcement reach out for additional details.
- You likely have an obligation to securely preserve certain information related to your report for one year in the case of an investigation. To ensure that access to the content is limited, take care not to store this information anywhere accessible to anyone but those within your organization responsible for legal requests.

<br />

- You should remove the content and notify Cloudflare of the removal.
- Once any preservation obligations have been fulfilled, you should remove the content from your website. This is especially important if Cloudflare's notice to you indicates that our block was unsuccessful.

---

## How do I have a block removed from my website?

To disable a block, either because you have determined that the blocked content is not CSAM (a false positive) or because you have taken down the blocked content, view [Blocked Content in the Security Center](/fundamentals/reference/report-abuse/blocked-content/) in the Cloudflare Dashboard and request reviews on the relevant blocks. A request to remove a block must be accompanied by a representation from you confirming that the blocked content is not CSAM or has been removed.

These actions are available to users with the following roles:

- Admin
- Super Admin
- Trust & Safety

---

## Additional Resources

[CSAM Scanning Tool Supplemental Terms](https://www.cloudflare.com/supplemental-terms/)

[National Center for Missing and Exploited Children (NCMEC)](https://www.missingkids.org/)

[NCMEC CyberTipline](https://www.missingkids.org/gethelpnow/cybertipline)

[INHOPE](https://www.inhope.org/)

# Development Mode

import { DashButton } from "~/components"

Development Mode temporarily suspends Cloudflare's edge caching and [Polish](/images/polish/) features for three hours unless disabled beforehand. Development Mode allows customers to immediately observe changes to their [cacheable content](/cache/concepts/default-cache-behavior/#default-cached-file-extensions) like images, CSS, or JavaScript.

:::note

To bypass cache for longer than three hours, use bypass cache in [Cache Rules](/cache/how-to/cache-rules/settings/#bypass-cache). 
:::

## Enable Development Mode

Development Mode temporarily bypasses Cloudflare's cache and does not purge cached files. To instantly purge your Cloudflare cache, refer to [purge cache](/cache/how-to/purge-cache/).

1. In the Cloudflare dashboard, go to the **Configuration** page.

   <DashButton url="/?to=/:account/:zone/caching/configuration" />

2. Toggle **Development Mode** to **On**.

# Using ETag Headers with Cloudflare

ETag headers identify whether the version of a resource cached in the browser is the same as the resource at the origin web server. A visitor's browser stores ETags. When a visitor revisits a site, the browser compares each ETag to the one it stored. Matching values cause a `304 Not-Modified HTTP` response that indicates the cached resource version is current. Cloudflare supports both strong and weak ETags configured at your origin web server.

## Weak ETags

Weak ETag headers indicate a cached resource is semantically equivalent to the version on the web server but not necessarily byte-for-byte identical.

:::note


When using weak ETag headers, it is necessary to disable certain features such as [Email Obfuscation](/waf/tools/scrape-shield/email-address-obfuscation/), [Automatic HTTPS Rewrites](/ssl/edge-certificates/additional-options/automatic-https-rewrites/), and [Replace insecure JavaScript libraries](/waf/tools/replace-insecure-js-libraries/) to prevent Cloudflare from removing the ETag headers set by your origin web server. For a comprehensive list of the features you need to disable, refer to the [Notes about end-to-end compression](/speed/optimization/content/compression/#notes-about-end-to-end-compression).


:::

## Strong ETags

Strong ETag headers ensure the resource in browser cache and on the web server are byte-for-byte identical. Use [Cache Rules](/cache/how-to/cache-rules/) to enable strong ETag headers.

### Behavior with Respect Strong ETags enabled

When you enable **Respect Strong ETags** in a cache rule, Cloudflare will use strong ETag header validation to ensure that resources in the Cloudflare cache and on the origin server are byte-for-byte identical.

However, in some situations Cloudflare will convert strong ETags to weak ETags. For example, given the following conditions:

* **Respect Strong ETags** is enabled
* [Brotli compression](/speed/optimization/content/compression/) is enabled
* The origin server's response includes an `etag: "foobar"` strong ETag header

The Cloudflare network will take the following actions, depending on the visitor's `accept-encoding` header and the compression used in the origin server's response:

<table-wrap>

| `accept-encoding`<br/>header from visitor | Compression used in origin server response | Cloudflare actions                                                                                       |
| ----------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `gzip, br`                                | GZIP                                       | Return GZIP-compressed response to visitor with strong ETag header: `etag: "foobar"`.                    |
| `gzip, br`                                | Brotli                                     | Return Brotli-compressed response to visitor with strong ETag header: `etag: "foobar"`.                  |
| `br`                                      | GZIP                                       | Decompress GZIP and return uncompressed response to visitor with weak ETag header: `etag: W/"foobar"`.   |
| `gzip`                                    | Brotli                                     | Decompress Brotli and return uncompressed response to visitor with weak ETag header: `etag: W/"foobar"`. |
| `gzip`                                    | (none)                                     | Return uncompressed response to visitor with strong ETag header: `etag: "foobar"`.                       |
| `gzip, br, zstd`                                    | Zstandard                                     | Return zstd-compressed response to visitor with strong ETag header: `etag: "foobar"`.    
| `gzip, br`                                    | Zstandard                                     | Decompress zstd and return br response to visitor with weak ETag header: `etag: W/"foobar"`.    
| `zstd`                                    | Brotli/GZIP                                     | Decompress zstd and return zstd response to visitor with weak ETag header: `etag: W/"foobar"`.                       |

</table-wrap>

Enabling **Respect Strong ETags** in Cloudflare automatically disables Rocket Loader, Email Obfuscation, and Automatic HTTPS Rewrites.

### Behavior with Respect Strong ETags disabled

When **Respect Strong ETags** is disabled, Cloudflare will preserve strong ETag headers set by the origin web server if all the following conditions apply:

* The origin server sends a response compressed using GZIP or Brotli, or an uncompressed response.
* If the origin server sends a compressed response, the visitor accepts the same compression (GZIP, Brotli), according to the `accept-encoding` header.
* [Rocket Loader](/speed/optimization/content/rocket-loader/), [Email Obfuscation](/waf/tools/scrape-shield/email-address-obfuscation/), and [Replace insecure JavaScript libraries](/waf/tools/replace-insecure-js-libraries/) features are disabled.

In all other situations, Cloudflare will either convert strong ETag headers to weak ETag headers or remove the strong ETag. For example, given the following conditions:

* **Respect Strong ETags** is disabled
* [Brotli compression](/speed/optimization/content/compression/) is enabled
* The origin server's response includes an `etag: "foobar"` strong ETag header

The Cloudflare network will take the following actions, depending on the visitor's `accept-encoding` header and the compression used in the origin server's response:

<table-wrap>

| `accept-encoding`<br/>header from visitor | Compression used in origin server response | Cloudflare actions                                                                                                                                |
| ----------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gzip, br`                                | GZIP                                       | Decompress GZIP and return Brotli-compressed response to visitor (since Brotli compression is enabled) with weak ETag header: `etag: W/"foobar"`. |
| `gzip, br`                                | Brotli                                     | Return Brotli-compressed response to visitor with strong ETag header: `etag: "foobar"`.                                                           |
| `br`                                      | GZIP                                       | Decompress GZIP and return Brotli-compressed response to visitor with weak ETag header: `etag: W/"foobar"`.                                       |
| `gzip`                                    | Brotli                                     | Decompress Brotli and return GZIP-compressed response to visitor with weak ETag header: `etag: W/"foobar"`.                                       |
| `gzip`                                    | (none)                                     | Compress origin response using GZIP and return it to visitor with weak ETag header: `etag: W/"foobar"`.                                           |
| `gzip, br, zstd`                                   | Zstandard                                    | Return zstd-compressed response to visitor with strong ETag header: `etag: "foobar"`.                                           |
| `gzip, br`                                   | Zstandard                                    | Decompress zstd and return uncompressed response to visitor with weak ETag header: `etag: W/"foobar"`.                                          |
| `zstd`                                   | Brotli                                    | Decompress zstd and return uncompressed response to visitor with weak ETag header: `etag: W/"foobar"`.                                         |

</table-wrap>

Refer to [Content compression](/speed/optimization/content/compression/) for more information.

## Important remarks

* You must set the value in a strong ETag header using double quotes (for example, `etag: "foobar"`). If you use an incorrect format, Cloudflare will remove the ETag header instead of converting it to a weak ETag. 

* If a resource is cacheable and there is a cache miss, Cloudflare does not send ETag headers to the origin server. This is because Cloudflare requires the full response body to fill its cache.

* If your origin (or R2) applies compression based on `accept-encoding`, the first compression type will be cached. Consider whether strong ETags fit your use case, or use cache key rules to handle different compression types.

# Reference

import { DirectoryListing } from "~/components"

Review the reference content to learn more about specific caching functionality.

<DirectoryListing />

# Range request behavior

Cloudflare can serve HTTP range requests from complete or partial cached files. [Origin Range Requests](/cache/how-to/cache-rules/settings/#origin-range-requests) let Cloudflare fetch eligible files from your origin in cache-aligned byte ranges.

## Request eligibility

Cloudflare uses the partial-file path only for a `GET` request that is eligible for caching when the request arrives. The origin response may later prove uncacheable. In that case, Cloudflare can still use origin range fetching, but does not store the response bytes.

`HEAD` requests and other methods do not use this path.

### HEAD requests

A `HEAD` request containing `Range` can return `200 OK`, `206 Partial Content`, `304 Not Modified`, or `416 Range Not Satisfiable`. A `HEAD` response never has a body. A `206 Partial Content` response includes `Content-Range`. When Cloudflare serves a complete cached file without Origin Range Requests, a matching `If-Range` validator can preserve a `206 Partial Content` response, while a mismatch returns `200 OK`. Cloudflare ignores `If-Range` for other `HEAD` request paths.

## Client response behavior

### Requests without Range

A successful, unconditional `GET` without `Range` returns the complete file as `200 OK`. On a cold cache miss, the first origin request also omits `Range`.

If only part of the file is cached, Cloudflare may request the missing bytes from the origin. The client still receives one complete `200 OK` response.

### One valid range

The following requests return `206 Partial Content` when successful:

- `Range: bytes=100-199`
- `Range: bytes=100-`
- `Range: bytes=-100`

Cloudflare shortens an end position that exceeds the file size. A suffix range larger than the file returns the complete file as `206 Partial Content`.

### Unsatisfiable ranges

Cloudflare returns `416 Range Not Satisfiable` for:

- An empty range, such as `bytes=`
- A zero-length suffix, such as `bytes=-0`
- Inverted range bounds
- A range starting at or beyond the end of the file
- A range against an empty, uncompressed file
- Overlapping or out-of-order ranges when Origin Range Requests applies

A Cloudflare-generated `416 Range Not Satisfiable` response has an empty body. It includes `Content-Range: bytes */<FILE_SIZE>`.

When Cloudflare serves a complete cached file without Origin Range Requests, it ignores overlapping or out-of-order ranges and normally returns the complete file as `200 OK`.

### Ignored Range headers

Cloudflare processes the request as a non-range request when:

- The range syntax cannot be parsed
- The range unit is not `bytes`
- A number is too large to parse
- The header contains more than 300 ranges, or exactly 300 ranges when Cloudflare serves a complete cached file without Origin Range Requests

This normally returns the complete file as `200 OK`. Conditional requests can return `304 Not Modified`, and origin errors still apply.

### Multiple ranges

When Origin Range Requests applies, between two and 300 valid, ascending, non-overlapping ranges return a multipart `206 Partial Content` response. Cloudflare omits individual ranges that cannot be satisfied. The response remains multipart if only one range remains. If no ranges remain, Cloudflare returns `416 Range Not Satisfiable`.

When Cloudflare serves a complete cached file without Origin Range Requests, one remaining satisfiable range produces a single-range `206 Partial Content` response.

### Conditional requests

`If-None-Match` and `If-Modified-Since` take precedence over `Range`. They can produce a `304 Not Modified` response.

Otherwise, an `If-Range` value that exactly matches the cached `ETag` or `Last-Modified` value preserves range behavior. A mismatch causes Cloudflare to ignore `Range` and use the complete-response path.

### Decompression

If Cloudflare must decompress a complete encoded response, it ignores `Range`. Cloudflare sends the complete, uncompressed body as `200 OK`. This applies to cached responses and responses received from the origin.

A range request for an empty, encoded response returns an empty `200 OK`, not `416 Range Not Satisfiable`.

## Origin request behavior

Cloudflare aligns origin range requests to 1 MiB cache boundaries. For example, if a client requests a 1 KiB range, Cloudflare may fetch a 1 MiB range from your origin and return only the requested 1 KiB to the client. Cloudflare may also split one client request into several single-range origin requests. The `Range` values seen by your origin can differ from the client's header. Consider this behavior when configuring origin logs, request limits, rate limits, or request signing.

Cloudflare adds `Accept-Encoding: identity` to every origin request generated by Origin Range Requests. This includes a complete-file `GET` handled by the feature. It does not disable normal compression between Cloudflare and the client.

### Partial response requirements

For Cloudflare to cache an origin `206 Partial Content` response, the response must meet these requirements:

| Response field | Requirement |
| --- | --- |
| `Content-Encoding` | The response must be unencoded. |
| `Content-Range` | Include a concrete range. Its start must match Cloudflare's request. Its end must match the requested end or the end of the file, whichever comes first. Include the complete file size. |
| `Content-Length` | Include a numeric value equal to the returned interval length. |
| `Transfer-Encoding` | Omit this header. |
| `ETag` | Optional. If present, its presence and exact value must remain consistent across responses for the same cached representation. |

The complete file size must also remain consistent across responses. Violations can prevent caching or end responses served to the client early.

### Origin responses that do not honor Range

If the origin ignores a Cloudflare-generated `Range` header and returns a complete `200 OK`, Cloudflare can use the response. Cloudflare must download the complete file and may then return the requested range to the client.

If the origin returns an encoded `206 Partial Content` response, Cloudflare does not cache it. Before the client response starts, Cloudflare may send that single response without caching. After the response starts, Cloudflare ends it early, and the client receives fewer bytes than promised.

Other incompatible origin responses during a response assembled from several requests can also end the client response early.

## Caching limitations

Origin Range Requests do not make an otherwise ineligible request or response cacheable. Cloudflare checks the [maximum cacheable file size](/cache/concepts/default-cache-behavior/#cacheable-size-limits) against the complete size from `Content-Range`. Requesting a small range does not bypass this limit.

Origin Range Requests are not supported with [Cache Reserve](/cache/advanced-configuration/cache-reserve/#limits).

# Always Online

Observe the following best practices when enabling Always Online with Internet Archive integration.

- **Allow requests from the Internet Archive IP addresses.** Origin servers receive requests from the Internet Archive IPs. Make sure you are not blocking requests from the Internet Archive IP range: `207.241.224.0/20` and `208.70.24.0/21`.
- **The Internet Archive does not consider your origin server's cache-control header.** When the Internet Archive is crawling sites, it will crawl sites regardless of their cache-control, since the Internet Archive does not cache assets, but archives them.
- **Consider potential conflicts with Cloudflare features that transform URIs.** Always Online with Internet Archive integration may cause issues with Cache Rules and other Cloudflare features that transform URIs due to the way the Internet Archive crawls pages to archive. Specifically, some redirects that take place at the edge may cause the Internet Archive's crawler not to archive the target URL. Before enabling Origin Cache Control, review [how Cloudflare caches resources by default](/cache/concepts/default-cache-behavior/) as well as any Cache Rules you have configured so that you can avoid these issues. If you experience problems, disable Always Online.
- **Do not block Known Bots or Verified Bots via a WAF custom rule.** If you block either of these bot lists, the Internet Archive will not be able to crawl.

Do not use Always Online with:

- API traffic.
- An [IP Access rule](/waf/tools/ip-access-rules/) or a [WAF custom rule](/waf/custom-rules/) that blocks the United States or
- Bypass Cache cache rules. Always Online ignores Bypass Cache cache rules and serves Always Online cached assets.

## Limitations

There are limitations with the Always Online functionality:

1. Always Online is not immediately active for sites recently added due to:
   - DNS record propagation, which can take 24-72 hours
   - Always Online has not initially crawled the website
2. Cloudflare cannot show private content behind logins or handle form submission (POSTs) if your origin web server is offline.

Always Online does not trigger for HTTP response codes such as [404](/support/troubleshooting/http-status-codes/4xx-client-error/error-404/), [503](/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-503/), or [500](/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-500/) errors such as database connection errors or internal server errors. This is because these status codes indicate the origin is reachable and responding — Always Online only activates when Cloudflare cannot connect to your origin at all (resulting in Cloudflare-generated [520–527](/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-520/) status codes). If your origin returns a 5xx error, the origin is online by definition and Always Online will not intervene.

## Frequently asked questions

1. How can I know if a page has been crawled?

   - You can go to the [Internet Archive](https://web.archive.org/) and search for the page URL to see if it has been crawled or not.
   - You can also check this via the [Internet Archive Availability API](https://archive.org/help/wayback_api.php).

2. Why were not pages x, y, and z crawled?

   - Since Cloudflare only requests to crawl the most popular pages on the site, it is possible that there will be missing pages. If you really want to archive a page, then you can visit the [Internet Archive](https://web.archive.org/save) save page and ask them to crawl a particular page.

3. What IP addresses do we need to allowlist to make sure crawling works?

   - IP Range: `207.241.224.0/20` and `208.70.24.0/21`. Note that this ip range belongs to Internet Archive and NOT Cloudflare, since it is the Internet Archive that does the crawling.

4. What user agent should the origin expect to see?
   - Currently the Internet Archive uses: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/605.1.15`.

# Bot Management cookie causes cache bypass in O2O setups

In [Orange-to-Orange (O2O)](/cloudflare-for-platforms/cloudflare-for-saas/saas-customers/how-it-works/) setups — where a SaaS provider uses [Cloudflare for SaaS](/cloudflare-for-platforms/cloudflare-for-saas/) and their customer also has their own Cloudflare zone — the `__cf_bm` Bot Management cookie returned from the origin-facing Cloudflare zone can cause the eyeball-facing zone to bypass cache. This occurs because the `Set-Cookie` header in the response triggers Cloudflare's default behavior of not caching responses with `Set-Cookie`.

If you are seeing unexpectedly low cache hit rates in an O2O setup with Bot Management enabled, this may be the cause.

# Dynamic content and login issues

Dynamic pages such as login forms, checkout flows, and authenticated application routes can break when they are cached too aggressively.

Common symptoms include:

- Users can load the login page, but the sign-in form fails after submission.
- Sessions do not persist after a successful sign-in.
- The origin sends a `Set-Cookie` header, but the browser never stores the cookie.
- A challenge page appears, but after solving it the user returns to the login page or loses form state.

## Cached login page strips session cookies

One common cause is a [Cache Rule](/cache/how-to/cache-rules/) or legacy Page Rule configured to cache dynamic HTML.

This usually happens when all of the following are true:

- The page is configured as **Eligible for cache** or **Cache Everything**.
- The response is dynamic HTML such as `/login` or `/account`.
- The origin sends a `Set-Cookie` header.
- An [Edge TTL](/cache/how-to/cache-rules/settings/#edge-ttl) or status-code TTL overrides origin cache directives.

In this configuration, Cloudflare can cache the response and remove the `Set-Cookie` header before the response is stored at the edge. As a result, the browser receives the login page but never gets the session cookie required for the next request.

### How to confirm

Check the response for the login page or other dynamic route.

If you see both of the following, the page is probably cached when it should not be:

- `CF-Cache-Status: HIT` or `CF-Cache-Status: EXPIRED`
- No `Set-Cookie` header in the response, even though your origin usually sets one

You may also see framework-specific failures after form submission, for example:

- A redirect back to the login page
- A `403` or `500` after sign-in
- CSRF validation errors
- Missing server-side session state

This issue is common with frameworks that rely on a session or CSRF cookie on the first page load, including JavaServer Faces, ASP.NET, PHP session handlers, Django, Rails, and Laravel.

### Resolution

Do not cache login pages or other authenticated HTML.

Instead:

1. Restrict **Eligible for cache** or **Cache Everything** to static paths only.
2. Add a more specific Cache Rule that bypasses or disables caching for routes such as `/login`, `/account`, `/cart`, `/checkout`, and application API paths.
3. If the origin must control caching, remove any Edge TTL override that forces the page to be cached.
4. Verify the fixed response now returns `CF-Cache-Status: DYNAMIC`, `MISS`, or `BYPASS`, and preserves `Set-Cookie`.

For more information on cookie behavior, refer to [Interaction of Set-Cookie response header with Cache](/cache/concepts/cache-behavior/#interaction-of-set-cookie-response-header-with-cache).

## Challenge loops on login or form flows

Security challenges can also interrupt dynamic flows.

Two common patterns are:

- A challenge is triggered on the initial `GET` request for the login page. The user solves the challenge, but the application loses the original session or CSRF context.
- A challenge is triggered on the `POST` request that submits the login form or other sensitive action. The browser may have to repeat the request after the challenge, which can break the original form submission.

### How to confirm

Check whether a [WAF custom rule](/waf/custom-rules/), [managed rule](/waf/managed-rules/), or [rate limiting rule](/waf/rate-limiting-rules/) applies to the login path.

If the issue only affects routes such as `/login`, `/signin`, `/checkout`, or `/api/auth/*`, and the application works when the challenge is disabled for those paths, the challenge is likely interrupting the flow.

### Resolution

Use one of the following approaches:

1. Exclude the login or form submission path from the challenge rule.
2. Narrow the rule expression so it applies to suspicious traffic only.
3. If you must protect the route, use a less disruptive control on the page load and apply stronger actions elsewhere in the flow.

When debugging, also verify that rules are not matching Cloudflare-generated paths such as `/cdn-cgi/*`.

For more information on challenge-related behavior, refer to [Rules troubleshooting](/rules/reference/troubleshooting/) and [Cloudflare WAF troubleshooting](/waf/troubleshooting/).

# edgeResponseBytes and cacheResponseBytes discrepancy

In [HTTP request logs](/logs/logpush/logpush-job/datasets/zone/http_requests/), `cacheResponseBytes` reflects the uncompressed response size from cache or origin. `edgeResponseBytes` reflects the final compressed response size sent to the client. Because Cloudflare applies compression (gzip, Brotli) before delivering to the client, `edgeResponseBytes` is typically smaller than `cacheResponseBytes`.

# Troubleshooting

import { DirectoryListing } from "~/components"

The following topics are useful for troubleshooting Cache issues.

<DirectoryListing />

# Investigate latency on tiered requests

Some requests for the same URL are slow while others are fast. If the slow requests have a `cf-cache-status` of `MISS` or `EXPIRED` and the fast requests have `HIT`, the difference may be explained by Tiered Cache: slow requests miss in the lower-tier data center and fetch through an upper-tier data center before reaching your origin. Fast requests are served directly from the lower-tier cache.

This page describes how to use Log Explorer to confirm whether Tiered Cache is involved and how to collect the data needed before contacting Support.

## Confirm Tiered Cache routing with Log Explorer

The [HTTP Requests](/logs/logpush/logpush-job/datasets/zone/http_requests/) dataset exposes fields that show whether a request was routed through an upper tier:

- **`CacheTieredFill`**: `true` when the lower-tier data center fetched from an upper-tier data center for this request.
- **`EdgeColoCode`**: the IATA airport code for the lower-tier data center that received the request.
- **`UpperTierColoID`**: the internal ID of the upper-tier data center used for the fill.
- **`CacheCacheStatus`**: the cache outcome (`HIT`, `MISS`, `EXPIRED`, `BYPASS`, and so on).
- **`OriginResponseDurationMs`**: the total time from when the lower-tier sent a request upstream until it received the full response. When `CacheTieredFill` is `true`, this duration includes the upper-tier lookup, any transit to your origin (also includes Argo Smart Routing transit time, if enabled), origin processing, and the response back. It is a measure of correlation, not isolation of a specific network hop.

## Correlate slow requests with upper-tier usage

To check whether the slow requests are the ones going through Tiered Cache:

1. Open [Log Explorer](/logs/) for the zone.
2. Filter by the affected URL or path and set a time window that includes both slow and fast samples.
3. Group by `CacheTieredFill` and compare `OriginResponseDurationMs` between the two groups.

If requests where `CacheTieredFill` is `true` show significantly higher `OriginResponseDurationMs` than those where it is `false`, Tiered Cache routing is correlated with the slowness.

4. Note the `EdgeColoCode` values for the slow requests and the `UpperTierColoID` values. These identify which lower-tier and upper-tier pair is involved.

:::note
`OriginResponseDurationMs` captures total upstream response time across all hops. It does not isolate a specific segment, such as transit between the upper tier and your origin, and does not prove congestion on a particular path. Use it to establish whether Tiered Cache routing correlates with the slow samples before escalating.
:::

## What to bring to Support

When you contact Cloudflare Support, include:

- The zone ID and affected URL or path.
- A Log Explorer export or screenshot showing slow samples (`CacheTieredFill: true`) and fast samples (`CacheTieredFill: false`), with `OriginResponseDurationMs`, `EdgeColoCode`, `UpperTierColoID`, and `CacheCacheStatus` visible.
- The time window in UTC during which you observed the issue.

Cloudflare Support and network engineering use this data to assess whether a routing change between the upper-tier data center and your origin is needed.

## Why turning off Tiered Cache does not fix the underlying cause

Turning off Tiered Cache routes all cache misses from every lower-tier data center directly to your origin. If the underlying issue is a congested or slow path from a specific upper-tier data center to your origin, turning off Tiered Cache may redistribute traffic rather than resolve the congestion, and removes the cache-fill efficiency benefits for the rest of your traffic.

If a specific upper-tier data center consistently produces high `OriginResponseDurationMs`, the appropriate fix is a routing or topology change. Consider:

- **[Smart Tiered Cache](/cache/how-to/tiered-cache/#smart-tiered-cache)**: selects upper tiers using origin-latency data, which can route around a slow upper-tier-to-origin path automatically.
- **[Custom Tiered Cache](/cache/how-to/tiered-cache/#custom-tiered-cache)**: lets you work with your account team to design a topology that avoids specific upper-tier data centers.

## Related resources

- [Investigate uncached responses](/cache/troubleshooting/investigating-uncached-responses/)
- [Tiered Cache](/cache/how-to/tiered-cache/)
- [HTTP Requests log fields](/logs/logpush/logpush-job/datasets/zone/http_requests/)

# Investigate uncached responses

If a URL you expected to be cached is served from the origin every time, the `cf-cache-status` response header identifies which cache decision Cloudflare made. Fetch the URL, inspect the header, and follow the section that matches the value you see:

- `DYNAMIC` — Cloudflare decided the request was not eligible for cache before it looked in the cache. Refer to [DYNAMIC — request not eligible for cache](#dynamic--request-not-eligible-for-cache).
- `BYPASS` — Cloudflare was ready to cache the response, but the origin response or configuration prevented it. Refer to [BYPASS — origin response is not cacheable](#bypass--origin-response-is-not-cacheable).
- `MISS` on multiple consecutive requests from the same client — the response is cacheable but keeps missing the cache. Refer to [Repeated MISS — cacheable but not in cache](#repeated-miss--cacheable-but-not-in-cache).

For any other status, refer to [Cache responses](/cache/concepts/cache-responses/) for the full list.

:::note
Cloudflare returns `BYPASS` — not `MISS` — for responses it chooses not to cache. `MISS` is reserved for cacheable responses that were not in cache at request time. Refer to the [BYPASS status changelog](/changelog/post/2026-05-26-bypass-status-for-uncacheable-responses/) for context.
:::

## Before you start

A recent [Purge Everything](/cache/how-to/purge-cache/purge-everything/), [purge by URL](/cache/how-to/purge-cache/purge-by-single-file/), [purge by prefix](/cache/how-to/purge-cache/purge_by_prefix/), [purge by tag](/cache/how-to/purge-cache/purge-by-tags/), or [purge by hostname](/cache/how-to/purge-cache/purge-by-hostname/) clears the cache. The next request in each data center repopulates cache and returns `MISS` before subsequent requests return `HIT`. If a purge ran recently, wait for the cache to repopulate before continuing.

## DYNAMIC — request not eligible for cache

Cloudflare made the "do not cache" decision at request time, before looking in the cache. Common causes:

- **The file extension is not in the [default cached file extensions](/cache/concepts/default-cache-behavior/#default-cached-file-extensions) list** — for example, `.html` or a JSON API response — and no rule enables caching for it. Add a [Cache Rule](/cache/how-to/cache-rules/) with **Eligible for cache** set to *Yes*.
- **A rule instructs Cloudflare to bypass cache.** Check whether a Cache Rule with the [Bypass cache](/cache/how-to/cache-rules/settings/#bypass-cache) setting, or a legacy `Cache Level: Bypass` [Configuration Rule](/rules/configuration-rules/) or [Page Rule](/rules/page-rules/), matches the URL. Use [Rules simulator](/rules/simulator/) to confirm which rules apply.
- **The request method is not `GET` or `HEAD`.** Cloudflare only caches these two methods.
- **[Development Mode](/cache/reference/development-mode/) is enabled on the zone.** Development Mode suspends cache for three hours and returns `DYNAMIC` for every response.

Once the request is eligible, subsequent responses reflect the response-time decision (`HIT`, `MISS`, `BYPASS`, and so on).

## BYPASS — origin response is not cacheable

The request was eligible for cache, but the origin response or configuration prevented Cloudflare from storing it. Common causes:

- **The response exceeds the [cacheable size limit](/cache/concepts/default-cache-behavior/#cacheable-size-limits) for your plan.** Split the object into smaller assets, or move to a plan with a higher limit. [R2](/r2/) is an origin storage alternative — it does not raise the CDN cacheable size limit.
- **The origin returned `no-store` or bare `private` in a `Cloudflare-CDN-Cache-Control` or `CDN-Cache-Control` header.** Cloudflare evaluates these headers ahead of `Cache-Control`, in the precedence `Cloudflare-CDN-Cache-Control` > `CDN-Cache-Control` > `Cache-Control`. An origin returning `Cache-Control: public, max-age=3600` together with `CDN-Cache-Control: no-store` produces `BYPASS`. Cloudflare does not forward `Cloudflare-CDN-Cache-Control` to the client, so it is not visible in the response — if you see `BYPASS` without a `CDN-Cache-Control` header, check what the origin actually sent, or have the origin drop the header and retest. A [Cache Rule](/cache/how-to/cache-rules/) with an [Edge Cache TTL](/cache/how-to/cache-rules/settings/#edge-ttl) setting that ignores origin cache-control overrides both directives. Refer to [CDN-Cache-Control](/cache/concepts/cdn-cache-control/) for the precedence rules.
- **`no-cache`, `max-age=0`, or `s-maxage=0` in `Cloudflare-CDN-Cache-Control` or `CDN-Cache-Control` do not produce `BYPASS`.** They produce `MISS` on the first request, then [`REVALIDATED`](/cache/concepts/cache-responses/#revalidated) or [`EXPIRED`](/cache/concepts/cache-responses/#expired) on subsequent requests.
- **The origin returned `Cache-Control: no-store` or bare `private`.** These directives block caching in either [Origin Cache Control](/cache/concepts/cache-control/) mode by default. Two exceptions: `Cache-Control: private="<header>"` with field names remains cacheable — Cloudflare drops only the named headers. And a [Cache Rule](/cache/how-to/cache-rules/) with an Edge TTL that ignores origin cache-control (**Edge TTL → Ignore cache-control header and use this TTL** or **Status code TTL**) overrides both directives, so a response with `no-store` plus that Edge TTL setting is cached.
- **The origin returned `Cache-Control: no-cache`, `max-age=0`, or `s-maxage=0`, and [Origin Cache Control](/cache/concepts/cache-control/) is disabled** (the default on Enterprise plans). With Origin Cache Control enabled (the default on Free, Pro, and Business plans), these directives cause Cloudflare to cache and revalidate the response instead, producing [`REVALIDATED`](/cache/concepts/cache-responses/#revalidated) or [`EXPIRED`](/cache/concepts/cache-responses/#expired). Refer to [Understand `no-store` and `no-cache` directives](/cache/concepts/cache-control/#understand-no-store-and-no-cache-directives) and the [Conditions](/cache/concepts/cache-control/#conditions) table.
- **The origin returned a `Set-Cookie` header.** By default, Cloudflare does not cache responses that include `Set-Cookie`. To cache the response, use one of the following:
  - Set an explicit Edge TTL on a [Cache Rule](/cache/how-to/cache-rules/) using **Edge TTL → Ignore cache-control header and use this TTL** or **Status code TTL**. Cloudflare ignores the origin's directives, strips `Set-Cookie`, and caches the response.
  - Have the origin return `Cache-Control: private="Set-Cookie"` or `no-cache="Set-Cookie"`. Cloudflare drops the named header and caches the rest.
  - Strip `Set-Cookie` before the cache decision, using a [Response Header Modification Transform Rule](/rules/transform/response-header-modification/).
  - On Enterprise plans with [Origin Cache Control](/cache/concepts/cache-control/) disabled, Cloudflare strips `Set-Cookie` and caches the response under the default cache level. A `Cache Level: Cache Everything` [Page Rule](/rules/page-rules/) or a Cache Rule with **Eligible for cache** set to *Yes* — either without an explicit Edge TTL — overrides this and returns `BYPASS`.

  Refer to [Interaction of `Set-Cookie` response header with Cache](/cache/concepts/cache-behavior/#interaction-of-set-cookie-response-header-with-cache) for the full matrix.
- **The origin returned `Vary: *`.** This value always bypasses cache, regardless of other configuration.
- **The request included an `Authorization` header and [Origin Cache Control](/cache/concepts/cache-control/) is enabled** (the default on Free, Pro, and Business plans). In that mode, the response is cacheable only if `Cache-Control` also includes `public`, `s-maxage`, or `must-revalidate`. On Enterprise plans with Origin Cache Control disabled, `Authorization` does not by itself prevent caching.


If [Tiered Cache](/cache/how-to/tiered-cache/) is enabled, an eligible request may be routed through an upper-tier data center before Cloudflare receives the origin response. Origin headers that produce `BYPASS` act after this upper-tier fetch has already happened, so they do not prevent it. To prevent requests from reaching the upper tier, use a request-time bypass rule — a [Cache Rule](/cache/how-to/cache-rules/settings/#bypass-cache) with **Bypass cache**, or a legacy `Cache Level: Bypass` [Configuration Rule](/rules/configuration-rules/) or [Page Rule](/rules/page-rules/).

Refer to [BYPASS](/cache/concepts/cache-responses/#bypass) for the reference definition of this status.

## Repeated MISS — cacheable but not in cache

`MISS` on the first request in each data center is expected — that request populates the cache. If the same URL keeps returning `MISS` across consecutive requests, one of the following is happening.

### Cache key variance

Cloudflare builds the cache key from the origin scheme, host, path, and query string by default. Cookies, headers, and device type can also contribute when configured. The scheme in the cache key is the scheme Cloudflare uses to reach the origin, not the scheme the client used — a zone with a single origin scheme serves HTTP and HTTPS client requests from the same cache entry.

If each real client request produces a different key, the cache never sees a repeat and every request is a `MISS`. Common patterns:

- **Query parameters that change per request** — session IDs, timestamps, or marketing tags such as `utm_*`. By default, every unique query string is a separate cache entry. Configure [Cache Rules](/cache/how-to/cache-rules/) or [Cache Key Settings](/cache/how-to/cache-keys/#cache-key-settings) to exclude or ignore parameters whose value changes per request. Sorting only canonicalizes parameter order — use it when requests differ only in the order of parameters, not when the values differ.
- **A [custom cache key](/cache/how-to/cache-keys/) includes a cookie or header with a value that changes per user.** The [Create custom cache keys](/cache/how-to/cache-keys/#create-custom-cache-keys) section warns that custom keys "may reduce your cache hit rate and result in cache sharding" — this is the same behavior.
- **[Cache by device type](/cache/how-to/cache-rules/examples/cache-device-type/) classifies clients differently than expected**, particularly for bots or clients with unusual `User-Agent` values.
- **[Vary in Cache Rules](/cache/how-to/cache-rules/settings/#vary) is configured** for a header, the origin lists that header in its `Vary` response header, and the action is `normalize` or `passthrough`. Each response variant is stored under a separate key. When the header has high cardinality — for example, an unnormalized `Accept-Language` value or a per-user header — the practical hit rate drops. The `bypass` action prevents caching entirely.

Two identical requests produce the same cache key and cannot reveal variance. To diagnose, use [Rules simulator](/rules/simulator/) to see the applied cache-key configuration and Vary action for the URL, then compare that against the request attributes (query string, cookies, headers, device type) that differ between real client requests.

### Eviction and low-traffic assets

Low-traffic assets can be evicted from the cache before the next request arrives. If two consecutive requests to the same URL from the same data center both return `MISS`, enable [Tiered Cache](/cache/how-to/tiered-cache/) or [Cache Reserve](/cache/advanced-configuration/cache-reserve/) to retain long-tail content longer.

If your requests reach different Cloudflare data centers, each produces its own first-request `MISS`. Compare the data center code — the last three characters of the `cf-ray` header — to confirm two responses came from the same data center. Different client networks can still reach the same data center, so a network change does not guarantee a different location.

## Confirm the response reaches cache

After adjusting configuration, request the URL twice from the same client and verify the expected outcome for your configuration:

- **Fresh, positive Edge TTL:** `cf-cache-status: HIT` and an `Age` header that increases on subsequent requests. `Age` is absent on the first request that fills the local data center's cache from an upper tier via [Tiered Cache](/cache/how-to/tiered-cache/) — the `HIT` reflects the upper tier, but the local data center has not served it from cache yet. Subsequent requests include `Age`, and with Tiered Cache the value can already be large because it reflects the object's age in Cloudflare's network-wide cache.
- **Origin returns `Cache-Control: no-cache` with [Origin Cache Control](/cache/concepts/cache-control/) enabled:** `cf-cache-status: REVALIDATED` when the origin confirms the cached copy is unchanged, or [`EXPIRED`](/cache/concepts/cache-responses/#expired) when the origin returns new content. Both indicate the response is cached. `must-revalidate` on its own does not force revalidation on every request — it only prevents serving stale content after the freshness TTL expires.

If the response is still `MISS` or `BYPASS` after these checks, capture two full responses (request and response headers, including `cf-ray` values) and open a support case. The `cf-ray` values are required to trace the request through the Cloudflare network.

## Related resources

- [Cache responses](/cache/concepts/cache-responses/) — reference for every `cf-cache-status` value.
- [Default cache behavior](/cache/concepts/default-cache-behavior/) — when Cloudflare caches successfully by default.
- [Cache Rules](/cache/how-to/cache-rules/) — configure Edge TTL, eligibility, and cache key.
- [Cache Analytics](/cache/performance-review/cache-analytics/) — measure hit rate and identify low-performing URLs.

## Related resources

- [Tiered Cache](/cache/how-to/tiered-cache/)
- [Investigate latency on tiered requests](/cache/troubleshooting/investigating-tiered-cache-latency/)

# Issues with MP4 videos on iOS and Safari

import { DashButton } from "~/components";

When traffic is proxied through Cloudflare, Safari on macOS and iOS devices may fail to load MP4 video files.

This issue occurs because Safari handles HTTP range requests differently than other browsers, particularly in how it processes ETags during video streaming.

Safari and iOS devices rely on HTTP range requests to support video features such as seeking to specific timestamps and resuming interrupted downloads.

When Cloudflare's caching layer processes these range requests with weak ETags, Safari may reject the cached response entirely, resulting in videos that fail to load or display as black screens.

To resolve this issue, configure two cache rules in the following order.

## 1. Create the strong ETags rule

Create a [cache rule](/cache/how-to/cache-rules/create-dashboard/) that applies to all MP4 files, marks them as eligible for cache, and turns on the Respect Strong ETags setting.

1. In the Cloudflare dashboard, go to the **Cache Rules** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

2. Select **Create rule** > **Cache rules**.
3. Enter a descriptive name for the rule in **Rule name**.
4. In the **When incoming requests match…** section, create a filter that applies to all MP4 files, for example `URI Full` `Wildcard` `*.mp4`.
5. Select **Eligible for cache** in the **Cache eligibility** section.
6. Select **+ Add Setting** for **Respect strong ETags** and turn on the toggle.
7. Select **Last** as **Place at**.

## 2. Create the bypass cache rule

Create another cache rule that applies to all MP4 files and bypasses cache entirely.

1. In the Cloudflare dashboard, go to the **Cache Rules** page.

   <DashButton url="/?to=/:account/:zone/caching/cache-rules" />

2. Select **Create rule** > **Cache rules**.
3. Enter a descriptive name for the rule in **Rule name**.
4. In the **When incoming requests match…** section, create the same filter for MP4 files, for example `URI Full` `Wildcard` `*.mp4`.
5. Select **Bypass cache** in the **Cache eligibility** section.
6. Select **Last** as **Place at**.

## Why this order matters

The first rule preserves strong ETags for MP4 files, which satisfies Safari's requirements for range request handling. The second rule bypasses cache so that Cloudflare forwards range requests to the origin server instead of serving cached responses with potentially mismatched ETags.

The first rule must appear above the second rule in the Cache Rules list.