For years, one stray Set-Cookie header could quietly wreck your cache hit ratio, and there was nothing you could do about it from the Cloudflare side. Your origin returns /static/app.js, some session middleware attaches a cookie to the response, and an asset that should be served from hundreds of edge data centers becomes uncacheable — for every visitor, on every request. Cloudflare’s answer, announced in its July 23, 2026 engineering post by Alex Krivit and Anthony Turcios, is Cache Response Rules: a new rule type that runs after your origin server replies but before Cloudflare writes anything to cache. This walkthrough covers what the rules can do, how to create your first one in the dashboard, and four ready-to-use recipes drawn from Cloudflare’s own examples.
The gap these rules close
Cloudflare already gives you a lot of caching control — but almost all of it operates on the request. Cache Rules decide, before Cloudflare ever talks to your origin, whether a response should be cached, under what cache key, and with what edge and browser TTLs. That request-time model makes sense: the “should we even look in cache?” question has to be answered before the origin fetch.
The problem is that many cache-killing signals only exist in the response. The Cache-Control directives your origin sends, its status code, ETag, Last-Modified, Set-Cookie, and any cache-tag headers all arrive after the request phase is over. As the announcement puts it, if the origin gets those headers wrong, “cache becomes decoration while the origin infrastructure costs skyrocket.”
Until now you had three workarounds, per Cloudflare: change the origin (often a weeks-long negotiation when a different team owns it), write a Worker that re-fetches and rewrites the response, or simply live with a worse hit ratio. Cache Response Rules add a fourth option that lives entirely on Cloudflare — no origin code changes required.
Concretely, a Cache Response Rule can do three things to an origin response before it enters cache:
- Rewrite
Cache-Controldirectives — set, remove, or override values likes-maxage,no-cache,public,immutable,must-revalidate, and others. - Manage cache tags — add or transform cache-tag values, including deriving them from other response headers.
- Strip headers — remove
Set-Cookie,ETag, orLast-Modifiedbefore Cloudflare’s cache ever sees them.
Step 1: Create the rule in the dashboard
Cloudflare’s post lays out the dashboard flow directly:
- Log in to the Cloudflare dashboard and select your zone.
- Go to Cache → Cache Rules.
- Select Create rule, then choose Cache Response Rule (this is the new option that sits alongside the existing request-phase Cache Rules).
- Give the rule a name and build an expression. The expression builder exposes both request fields (path, extension, headers) and response fields (status code, response headers) — mixing the two is what makes this rule type powerful.
- Choose an action: Modify cache-control directives, Modify cache tags, or Strip headers.
- For directive changes, toggle Cloudflare only if you want the change to apply only to how Cloudflare caches the asset, leaving the
Cache-Controlheader your visitors’ browsers receive untouched. - Save as a draft to iterate safely, or deploy the rule directly.
That draft option is worth using on a production zone: you can build and review the expression without it taking effect, then deploy once you have verified the match logic.
Step 2: Start with the highest-value recipe — strip Set-Cookie from static assets
This is the classic cache killer, and Cloudflare’s first worked example. Session middleware on the origin attaches a Set-Cookie to every response, including images, fonts, and bundles that are identical for every visitor.
- Expression:
http.request.uri.path.extension in {"js" "css" "woff2" "woff" "ttf" "png" "jpg" "svg"} - Action: Strip headers →
Set-Cookie
Stripping the cookie for known-static extensions converts those responses to cacheable without touching origin code. Cloudflare’s caveat: only do this for asset types where the cookie is not semantically required. If your origin genuinely uses cookies to vary behavior on those URLs, scope the rule to specific paths instead of blanket extensions.
Step 3: Cache longer at Cloudflare than in the browser
The second recipe decouples edge lifetime from browser lifetime — something that previously required your origin to emit a separate CDN-Cache-Control header.
- Expression:
http.request.uri.path.extension in {"js" "css" "woff2"} - Action: Modify cache-control directives:
s-maxage: set to2592000(30 days), with Cloudflare only enabledimmutable: setmax-age: set to86400(1 day), Cloudflare only disabled
Cloudflare holds the asset for a month and serves it from cache, while browsers see max-age=86400 and revalidate daily. In the API, that Cloudflare-only behavior appears as a cloudflare_only: true flag on the directive — for example "s-maxage": { "operation": "set", "value": 86400, "cloudflare_only": true }. One warning from the post: immutable tells browsers not to revalidate even on an explicit refresh, so pair it only with versioned or hashed filenames like app.3f9c2b.js.
Step 4: Override no-cache on a path you know is static
Framework defaults sometimes attach Cache-Control: no-cache to every response, including safe static paths.
- Expression:
starts_with(http.request.uri.path, "/static/") and http.response.code eq 200 - Action: Modify cache-control directives: remove
no-cache, sets-maxageto3600with Cloudflare only enabled
Note the response field in the expression — the rule only rewrites successful responses, so error pages keep their original directives. Cloudflare’s caveat applies here too: be honest about what is actually static. If /static/ sometimes serves user-specific content, narrow the match by extension, content type, or a response header signal.
Step 5: Translate cache tags during a CDN migration
If you are migrating from another CDN, your origin tooling may emit cache tags in the other vendor’s header format. Instead of shipping an origin release, translate in the response phase:
- Expression:
any(http.response.headers.names[*] == "Surrogate-Keys") - Action: Modify cache tags → add →
split(http.response.headers["Surrogate-Keys"][0], ",", 64)
Purge-by-tag on Cloudflare starts working immediately, while the origin keeps emitting its existing header. Detail worth knowing: the third argument to split() is a limit of 1–128 on the resulting array size — it has nothing to do with the separator.
Step 6: Verify the rule is actually working
After deploying, confirm the behavior from the outside:
- Request an affected asset twice with
curl -sI https://yourdomain.com/static/app.jsand check thecf-cache-statusheader. You want to see it move fromMISS(orBYPASSbefore the rule) toHITon the repeat request. - Confirm the stripped header is gone from the response — no
set-cookieline should appear for matched static assets. - If you used the Cloudflare-only toggle, check that the
cache-controlheader the browser receives still shows your intended downstream value (for examplemax-age=86400), not the edge value.
If the status stays BYPASS, re-check the expression in the rule builder — extension matching is exact, and a mismatched path prefix is the most common culprit.
A worked example: the accidental cookie on a font file
To make the first recipe concrete, walk through the exact failure it fixes. Say your site serves /fonts/inter.woff2 behind Cloudflare, and your application framework runs a session middleware globally. Every response — HTML pages and static files alike — leaves the origin with a header block like this:
HTTP/1.1 200 OK
Content-Type: font/woff2
Cache-Control: public, max-age=31536000
Set-Cookie: session=abc123; Path=/; HttpOnly
The Cache-Control header says “cache me for a year,” but the Set-Cookie header marks the response as per-visitor, so Cloudflare will not store it. Every single visitor pulls that font from your origin, paying full origin latency and bandwidth even though the bytes are identical for everyone. Before request-time rules can help, the damage is already in the response — which is exactly why this needed a response-phase fix.
With the Step 2 rule deployed, the same origin response has its Set-Cookie stripped before the cache write. The first visitor populates the edge cache; everyone after gets a cf-cache-status: HIT served from the nearest data center, and your origin sees one request instead of thousands. No middleware configuration change, no ticket to the backend team, no Worker to maintain.
Where this fits in your existing setup
Think of it as two phases answering two questions. Request-phase Cache Rules answer: given this request, should Cloudflare check cache, and under what key? Response-phase Cache Response Rules answer: given what the origin actually sent back, how should this response be cached? The old Page Rules era bundled caching with redirects and security into one overloaded, request-evaluated primitive; Cloudflare has since split those concerns apart, and Cache Response Rules fill the last gap — the response side — without a Worker.
A sensible rollout order for most sites: start with the Set-Cookie strip on static extensions (biggest hit-ratio win, lowest risk), add the split edge/browser TTL rule for hashed bundles, and only reach for no-cache overrides and cache-tag translation when you have a specific origin constraint you cannot change. Save each rule as a draft first, deploy one at a time, and watch cf-cache-status after each deployment so you can attribute any change in behavior to the rule that caused it.
