Cloudflare

cf-cache-status explained: every value, what it costs you, and how to fix it

July 2026 · 8 min read · ← All posts

cf-cache-status is the single most informative header Cloudflare attaches to a response. One word tells you whether the request was answered in milliseconds from a data center near the visitor — or traveled all the way to your origin server and back. This guide explains every value, the difference between the confusing ones (DYNAMIC vs. BYPASS, MISS vs. EXPIRED), and the concrete fix for each.

How to check it

Three ways, from quick to thorough:

Every cf-cache-status value

StatusWhat happenedCost to the visitor
HITServed from the edge cache. Origin never contacted.✅ None — this is the goal
MISSCacheable, but not in this colo's cache. Fetched from origin, stored for next time.⚠️ Full origin round trip
EXPIREDWas in cache, but the edge TTL ran out. Re-fetched from origin.⚠️ Full origin round trip
STALEServed the expired copy while fetching a fresh one in the background.✅ Fast, content slightly old
UPDATINGLike STALE, but the refresh is already in flight (popular objects).✅ Fast
REVALIDATEDExpired entry checked against origin — still fresh (304), served from cache.✅ Cheap origin ping only
DYNAMICNot eligible for caching under default rules. Every request goes to origin.❌ Never cached
BYPASSEligible, but explicitly opted out (no-store, private, cookie, bypass rule).❌ Never cached
NONE / UNKNOWNNo cache lookup happened — Workers subrequests, redirects, errors.— depends
(header absent)The response didn't pass through Cloudflare's cache at all.❌ Check your DNS/proxy status

DYNAMIC: the one that surprises everyone

Paste any normal web page into a header checker and you'll almost certainly see DYNAMIC. That's not a bug — it's Cloudflare's default: only static file extensions are cached (images, CSS, JS, fonts, PDFs, …). HTML is not on the list. Your carefully tuned origin cache headers don't change this; Cloudflare ignores them for ineligible content types.

Two ways to make HTML cacheable:

  1. Cache Rule (dashboard → Caching → Cache Rules): match your hostname or path, set "Eligible for cache", and choose an edge TTL. Exclude logged-in traffic by cookie:
    (http.host eq "example.com"
     and not http.cookie contains "session")
  2. Cloudflare-CDN-Cache-Control header from your origin — it instructs Cloudflare's cache specifically, without affecting browsers:
    Cloudflare-CDN-Cache-Control: max-age=14400
    Cache-Control: max-age=0, must-revalidate

The full walkthrough, including Workers Cache and APO, is in our Cloudflare cache warming guide.

BYPASS: cacheable, but opted out

BYPASS looks similar to DYNAMIC but has the opposite cause: the content was eligible, and something explicitly told Cloudflare not to cache this response. The usual suspects:

Fix the origin header or the stray cookie and BYPASS becomes MISS on the next request — then HIT after that.

MISS and EXPIRED: the geography problem

A MISS isn't a configuration error — it's the price of a cold cache. What most people underestimate is how often caches are cold:

Organic traffic only fixes this for your most popular pages in your busiest regions. For everything else, the first visitor in each region pays the origin round trip — unless you fill the caches proactively. That's what warmup.rocks does: it requests every URL in your sitemap from 90+ edge locations on a schedule, so the cache is already warm when a real visitor arrives, and shows you the resulting hit ratio per location after every run.

STALE, UPDATING, REVALIDATED: the good expired states

These three mean your configuration is working well: instead of making a visitor wait for origin, Cloudflare served the slightly-old copy (STALE, UPDATING) or confirmed freshness with a cheap 304 (REVALIDATED). You get them by sending validators (ETag / Last-Modified) and, on Enterprise, stale-while-revalidate directives. No action needed — count them as hits in your monitoring.

Debug checklist

  1. No cf-cache-status at all? The hostname isn't proxied (grey cloud in DNS) or the request bypasses Cloudflare entirely.
  2. DYNAMIC on HTML? Expected default — add a Cache Rule or Cloudflare-CDN-Cache-Control.
  3. BYPASS? Find the no-store/private directive or the Set-Cookie on the response.
  4. MISS that never becomes HIT? Check cf-ray — you're probably hitting different colos per request. Test twice against the same colo before concluding anything.
  5. HIT here, slow for users elsewhere? Run the multi-location check — other regions are likely cold, and cache warming is the fix.

Make every location report HIT

warmup.rocks warms your pages in 90+ Cloudflare edge locations on a schedule and shows the cf-cache-status result per URL, per colo, after every run.

Start your 7-day free trial