The right moment to fix a cache problem
Cache misses rarely start at the request. A visitor asks for /static/app.js, Cloudflare checks cache, misses, and goes to the origin. The origin sends the file back—and somewhere in the response headers sits a stray Set-Cookie or a Cache-Control: no-cache that was meant for a different context. The asset that should have been cached at every edge location is now uncacheable. Multiply that by every visitor and every request with the same accidental header, and the origin is serving traffic the edge should have absorbed. The response headers are the source of truth for the cache, and when they're wrong, nothing done at request time can fix it.
Cache Response Rules run at the one moment that matters: after the origin's response arrives at Cloudflare, but before it is written to cache. With them, you can rewrite Cache-Control directives, manage cache tags, and strip headers like Set-Cookie, ETag, and Last-Modified before Cloudflare's caching layer ever sees them. The fix is entirely on Cloudflare, with no origin code changes required.
Filling the response-phase gap
Cloudflare's cache control surface has evolved from Page Rules—which mixed caching with redirects, security, and other behaviors in a single request-time primitive—into dedicated Cache Rules, CDN-Cache-Control, Origin Cache Control, custom cache keys, and other tools. Nearly all of them share a common trait: they operate during the request phase, before Cloudflare talks to the origin.
That makes sense for the most important caching decision—should we look this up in cache, and under what key?—which has to be answered before the origin round trip. But it leaves a gap. Response attributes like Cache-Control directives, status codes, ETag, Last-Modified, Set-Cookie, and cache tags are not available when request-time rules run. They arrive with the origin's response.
Previously, if the origin's response needed changing and you couldn't fix it at the source, three options remained: change the origin, write a Worker that re-fetches and rewrites the response, or accept a worse hit ratio. Each costs engineering time, added latency, or wasted bandwidth. Cache Response Rules add a fourth option: a ruleset that can modify the origin's response before it hits Cloudflare's cache.
Request phase vs. response phase
The cleanest way to think about the new rule type is as a second phase that answers a different question from Cache Rules:
- Cache Rules run in the request phase, before Cloudflare talks to the origin. They decide whether to cache (eligible vs. bypass), what the object is (the cache key), and how to cache (edge TTL, browser TTL, serve-stale). All of this is settled using only request information.
- Cache Response Rules run in the response phase, after the origin replies but before the response is stored. They decide now that the origin has responded, should we adjust how we cache it? They can rewrite the "how" by stripping cache-breaking headers, changing origin
Cache-Controldirectives, or setting cache tags. When a Cache Rule and a Cache Response Rule conflict, the response rule wins.
There are limits. The response phase cannot change the cache key—that is already fixed by request time. It can change whether and how Cloudflare caches: a response rule can set no-store to make a cacheable object non-cacheable, or strip Set-Cookie to make non-cacheable content eligible. But a request that was already forwarded to the origin cannot be made cacheable retroactively—the latency cost of the miss has already been paid.
What you can do
Strip headers that break caching
The set_cache_settings action removes things like Set-Cookie, ETag, or Last-Modified from the origin response before Cloudflare evaluates it for caching:
"action_parameters": {
"strip_etags": true,
"strip_set_cookie": true,
"strip_last_modified": true
}
This targets the Set-Cookie-on-a-static-asset problem directly. Origin frameworks often attach session cookies to every response—including assets that shouldn't be session-bound—for load balancing or other purposes. Stripping Set-Cookie at the response phase makes those assets cacheable again without touching the origin or anything upstream.
These rules fire on responses that aren't cacheable at all. Stripping Set-Cookie from a dynamic response still applies, letting you control what the client sees even when nothing is stored. Stripping both ETag and Last-Modified has a side effect worth noting: it enables Smart Edge Revalidation for that response. If the rule later adds new validators, however, Smart Edge Revalidation will not apply to browser conditional requests.
Manage cache tags
set_cache_tags lets you add, remove, or set cache tags used for purge-by-tag on the response. Tags can be static:
"action_parameters": {
"operation": "set",
"values": ["product-catalog", "storefront"]
}
Or computed from a response header:
"action_parameters": {
"operation": "add",
"expression": "split(http.response.headers[\"Surrogate-Keys\"][0], \",\", 64)"
}
The computed form is especially useful during a CDN migration. If your previous CDN sent surrogate keys in a header like Surrogate-Keys with a comma delimiter, you can translate them into Cloudflare's Cache-Tag format during the response phase. The third split() argument is the limit—between 1 and 128 elements. A value of 1 would return the entire header as one tag; use a value comfortably larger than the realistic tag count per response. Purge-by-tag then works with Cloudflare's usual sub-150 ms global purge latency.
Modify Cache-Control directives
The set_cache_control action does the most heavy lifting. You can set or remove individual directives:
- Duration directives:
max-age,s-maxage,stale-if-error,stale-while-revalidate - Qualified directives:
private,no-cache(with optional header-name qualifiers) - Boolean directives:
no-store,no-transform,must-revalidate,proxy-revalidate,must-understand,public,immutable
Each directive can also be set with cloudflare_only: true:
"action_parameters": {
"s-maxage": {
"operation": "set",
"value": 86400,
"cloudflare_only": true
}
}
When cloudflare_only is true, the directive affects how Cloudflare caches the response, but the Cache-Control value sent downstream to the browser is unchanged. This is the response-phase equivalent of what CDN-Cache-Control provides at the origin—cache an asset at the edge for 24 hours while telling the browser something different—except you can now configure it from the Cloudflare dashboard rather than asking the origin to send an extra header.
Cache Response Rules in practice
Cache Response Rules let you adjust what Cloudflare actually stores after the origin responds, rather than merely influencing the request before it hits the origin. The action happens in the response phase, which gives you several practical patterns worth applying.
Strip Set-Cookie from static asset extensions
Expression: http.request.uri.path.extension in {"js" "css" "woff2" "woff" "ttf" "png" "jpg" "svg"}
Action: set_cache_settings
strip_set_cookie: true
Why it works: A session middleware on the origin that attaches a Set-Cookie to every response is the most common culprit behind "this should be cacheable but isn't." Removing the header for known-static extensions makes those responses cacheable with zero origin changes.
Caveat: Restrict this to asset types where the cookie carries no semantic meaning. If your origin uses cookies to drive variant behavior on those URLs — rare but possible — keep the stripping selective or scope it by path.
Long cache at Cloudflare, short cache in the browser
Expression: http.request.uri.path.extension in {"js" "css" "woff2"}
Action: set_cache_control
s-maxage: set 2592000 (30 days), cloudflare_only=true
immutable: set
max-age: set 86400 (1 day), cloudflare_only=false
Why it works: Cloudflare stores the asset for a month and serves it straight from cache. Browsers read max-age=86400 and revalidate after a day. The two cache lifetimes are decoupled without touching the origin at all.
Caveat: The immutable directive stops browsers from revalidating even on an explicit refresh, so pair it only with versioned or hashed filenames.
Override no-cache on a path you know is static
Expression: starts_with(http.request.uri.path, "/static/") and http.response.code eq 200
Action: set_cache_control
no-cache: remove
s-maxage: set 3600, cloudflare_only=true
Why it works: Framework defaults often slap no-cache onto every response. For a path like /static/* that you know is safe, strip the directive and assign your own TTL — at Cloudflare only — leaving what the origin and downstream caches see completely unchanged.
Caveat: Be honest about what's actually static. If /static/ sometimes serves user-specific content, narrow the match using extension, response-header signal, or content type instead.
Translate cache tags during a CDN migration
Expression: any(http.response.headers.names[*] == "Surrogate-Keys")
Action: set_cache_tags
operation: add
expression: split(http.response.headers["Surrogate-Keys"][0], ",", 64)
Why it works: Origin tooling often emits cache-tag headers in another vendor's format. Rather than asking the origin team for a release that adds Cloudflare's Cache-Tag, translate the existing header in the response phase — purge-by-tag works immediately.
Caveat: The third argument to split() is the limit on the resulting array size (1–128), not a separator setting.
Setting up Cache Response Rules
Dashboard
- In the Cloudflare dashboard, go to Cache > Cache Rules.
- Select Create rule, then Cache Response Rule.
- Name the rule and build an expression. The expression builder exposes both request and response fields.
- Pick an action: Modify cache-control directives, Modify cache tags, or Strip headers.
- For directive changes, toggle Cloudflare only when the adjustment should apply solely to Cloudflare's view of the asset.
- Save as a draft to iterate, or deploy immediately.
API
Rules in this phase are defined at the following endpoint:
/zones/{zone_id}/rulesets/phases/http_response_cache_settings/entrypoint
Cache Rules operate on the request; Cache Response Rules operate on the response. Together they give you more granular control over the cache. Both are available on all plans today — you can try them in the dashboard directly.



