Overview
Cloudflare’s edge cache is the cheapest performance win on the platform. Cache Rules replaced the older Page Rules in 2023 and offer per-request matchers, custom cache keys, tiered caching, and explicit bypass logic. Stop writing Page Rules; new work belongs in Cache Rules, Origin Rules, or Transform Rules.
Use Cache Rules; Page Rules are deprecated
Page Rules still execute on existing zones, but Cloudflare stopped accepting new feature requests and is sunsetting the system. Cache Rules are the replacement.
- Per-zone Cache Rules support up to 50 rules on the free plan and far more on paid.
- The matcher uses the same expression engine as WAF and Transform Rules:
http.request.uri.path eq "/api/health". - Order matters; rules evaluate top to bottom, and the first match wins for cache eligibility.
- Drift between Page Rules and Cache Rules is a common bug. Migrate in one pass and disable the old rules.
For migrations larger than a handful of rules, drive the change through Terraform with the cloudflare_ruleset resource and review the diff before applying.
Customize the cache key to match content boundaries
The cache key is the tuple Cloudflare hashes to look up a cached response. The default key is host plus path plus query string; everything else (headers, cookies) is ignored.
- Include a header in the key when the response varies by it:
Accept-Language,X-Theme,CF-Device-Type. - Include specific query params; exclude tracking params (
utm_*,gclid,fbclid) to avoid cache fragmentation. - Include a cookie only when authentication or A/B routing demands it. A user-specific cookie destroys the cache hit rate; reach for a Worker or a separate path instead.
# Cache Rule (expression)
http.request.uri.path matches "^/products/.*"
# Cache key settings
include query params: id, color
include header: CF-Device-Type
ignore query params: utm_source, utm_medium, utm_campaign, gclid
Set cache eligibility to “Eligible for cache” and pick an Edge TTL that matches how fast the content changes.
Turn on tiered caching for upper-tier hits
Tiered caching routes cache misses through an upper-tier PoP before they hit the origin. The upper tier holds the canonical copy; lower tiers fill from it.
- Smart Tiered Caching picks the upper tier automatically based on origin location. Turn it on; it is free on paid plans.
- The benefit grows with origin distance and traffic from many regions. A site with a US-East origin and global traffic sees a 30 to 60 percent reduction in origin requests.
- Pair tiered caching with a long Edge TTL (
max-age86,400 or longer) so the upper tier holds the asset between regional misses.
For static assets fronted by github-pages or cloudflare-r2, tiered caching plus a long Edge TTL eliminates almost all origin traffic.
Bypass cache for authenticated or dynamic paths
The default cache passes through Cache-Control: no-store, cookies named _session, and anything the origin marks private. Make the bypass explicit anyway.
# Cache Rule: bypass API
http.request.uri.path starts_with "/api/"
=> Cache eligibility: Bypass cache
# Cache Rule: bypass authenticated dashboard
http.request.uri.path starts_with "/app/" and
http.request.cookies["session"] != ""
=> Cache eligibility: Bypass cache
Bypass is safer than guessing at TTLs for dynamic content. The cost is a full trip to origin on every request; pair the bypass with cache headers from the origin (Cache-Control: private, no-store) so downstream caches behave too.
Read cf-cache-status to diagnose cache behavior
Every response from Cloudflare carries a cf-cache-status header. Treat it as the source of truth.
HIT: served from the edge cache. The goal.MISS: not cached at the edge; fetched from origin and now cached.EXPIRED: was cached but the TTL elapsed; refetched from origin.REVALIDATED: stale-while-revalidate; served stale while a background refresh runs.DYNAMIC: not eligible for cache (cookies, method, headers).BYPASS: matched a bypass rule.UPDATING: stale-while-error; served stale because origin returned an error.
A site that ships mostly DYNAMIC is missing a Cache Rule. A site with high EXPIRED rate needs a longer Edge TTL. A site with BYPASS on assets has a misconfigured rule.
Purge surgically, not aggressively
Cloudflare offers four purge modes; pick the smallest one that works.
- Purge by URL: invalidate a single asset. Use this 95 percent of the time, wired into the deploy pipeline.
- Purge by cache tag (Enterprise): tag responses with
Cache-Tag: home,navfrom the origin, then purge by tag on content updates. - Purge by hostname: clear a single subdomain. Useful for staging cutovers.
- Purge everything: clears the entire zone. Costs minutes of origin load and a temporary latency spike; avoid as a routine action.
# Purge specific URLs after deploy
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/purge_cache" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"files":["https://example.com/assets/app.css","https://example.com/index.html"]}'Wire targeted purges into the deploy pipeline; the github-pages or cloudflare-workers release job already knows which files changed.