What Does cf-cache-status DYNAMIC, BYPASS, or MISS Mean?

Putting a site behind a proxy does not make it a cached site. One response header names the decision Cloudflare made, at which moment it was made, and therefore which setting is worth changing.

Updated September 4, 2026 · 8 min read

cf-cache-status names the cache decision. DYNAMIC means the request was never eligible — HTML and JSON are not cached by default. BYPASS means the origin response blocked storage. MISS means it was cacheable but not stored yet.

Ask why Cloudflare is not caching your site and the honest answer is usually that it is working as designed. The assumption underneath the question — that traffic routed through Cloudflare is cached traffic — is the part that is wrong. Static assets are cached by default, pages are not, and there are several distinct ways a response ends up served from your server, each with a different fix. Guessing between them is how people stack rules that cancel each other out.

Read the cf-cache-status header before changing any setting

Every proxied response carries this header, and it names the decision that was made. Tooling spells it inconsistently — CF-Cache-Status in Cloudflare’s own documentation, cf-cache-status on the wire, plain cache status in most dashboards — but it is one header carrying one value per response. Fetch the URL and read it before touching anything:

curl -sSI https://example.com/ | grep -iE '^(cf-cache-status|age):'

The Age header is a useful companion, with one caveat. Cloudflare sets it on HIT, STALE, and UPDATING responses, reporting how many seconds the asset has been in cache since it was admitted or last revalidated. It is absent on MISS, DYNAMIC, BYPASS, and NONE/UNKNOWN — but if your origin sends its own Age on an uncacheable response, Cloudflare passes that value through unchanged. So Age corroborates cf-cache-status; it never substitutes for it. If both headers are absent entirely, the hostname is probably not proxied at all — see proxied vs DNS only for that case, because a DNS-only record never reaches a cache in the first place.

What each cf-cache-status value means

ValueWhat happenedServed from
HITThe asset was in cache and still freshCache
MISSCacheable, but not present in this data center yetOrigin
DYNAMICJudged not eligible for cache before any lookup happenedOrigin
BYPASSEligible, but the response itself could not be storedOrigin
EXPIREDA stale copy existed; fresh content was fetchedOrigin
STALEExpired copy served because the origin could not be reachedCache
REVALIDATEDThe origin confirmed the cached copy was unchangedCache
UPDATINGExpired copy served while a refresh runs in the backgroundCache
NONE/UNKNOWNThe response was produced before cache was ever consultedNeither

That last row catches people out. A Worker that answers without a subrequest, a blocked WAF request, and a redirect rule all short-circuit ahead of the cache, so they are labelled NONE/UNKNOWN rather than describing a cache outcome at all. REVALIDATED is also rarer than it used to be: with asynchronous stale-while-revalidate, a refresh that once made the visitor wait now usually reports UPDATING or HIT instead. The full reference lives in the cache responses documentation.

DYNAMIC vs BYPASS: two decisions, not one

How Cloudflare arrives at each cf-cache-status valueRequest reaches CloudflareEligible for cacheat request time?DYNAMICOrigin responsecacheable?BYPASSAlready in thisdata center's cache?MISSHITyesyesyesnonono
DYNAMIC and BYPASS look alike in a browser, but they are decided at different moments and have different fixes.

DYNAMIC and BYPASS both mean “not cached”, and they are routinely treated as the same problem. They are not. DYNAMIC is decided at request time, from the URL and your rules, before the cache is consulted. BYPASS is decided at response time, once your origin has answered and its headers have been read. One is fixed in your Cloudflare configuration; the other is usually fixed on your server.

DYNAMIC: the request never reached the cache

Four causes account for nearly all of them:

  • The extension is not on the default list. Eligibility is judged by file extension, not MIME type, and the default list covers images, fonts, archives, CSS, and JS — but deliberately excludes HTML and JSON, since pages and API responses are frequently personalised. A URL with no extension at all, such as /pricing, falls in the same bucket.
  • A rule says bypass. A Cache Rule set to bypass, or a legacy configuration or page rule with cache level set to bypass, produces DYNAMIC for everything it matches. When several rules overlap, Rule Trace shows which one actually applied instead of leaving you to reason about ordering.
  • The method is not GET or HEAD. Nothing else is cached, so a page whose content arrives via POST cannot be cached no matter how it is configured.
  • Development Mode is on. It suspends caching zone-wide for three hours and returns DYNAMIC for every response. It is the first thing to check when caching stopped working “for no reason” earlier today.

BYPASS: eligible, but the response said no

Here the request cleared eligibility and the answer from your origin is what prevented storage. The usual culprits:

In the responseWhy it blocks caching
Set-CookieTreated as per-visitor by default, so the response is not stored
Cache-Control: no-store or bare privateBlocks storage regardless of plan or origin cache-control mode
Cache-Control: no-cache, max-age=0On Free, Pro, and Business these produce revalidation rather than a bypass; with origin cache-control disabled, the default on Enterprise, they block it
Vary: *Always bypasses, whatever else is configured
CDN-Cache-Control: no-storeRead ahead of Cache-Control, so it wins even when Cache-Control declares the response public and cacheable
Body over the size limit512 MB on Free, Pro, and Business; 5 GB by default on Enterprise
Request had AuthorizationWith origin cache-control enabled, cacheable only if the response also carries public, s-maxage, or must-revalidate

Three of these are worth knowing precisely. First, the header Cloudflare actually obeys is not always the one you set: it reads Cloudflare-CDN-Cache-Control, then CDN-Cache-Control, and only then Cache-Control — so an origin returning Cache-Control: public, max-age=3600 alongside CDN-Cache-Control: no-store bypasses cache, and the Cache-Control header you keep re-checking is the wrong one to look at. Second, whether no-cache blocks caching depends on origin cache-control, which is enabled by default on Free, Pro, and Business plans and disabled by default on Enterprise — so identical origin headers behave differently on different plans. Third, since a change in May 2026, responses Cloudflare refuses to cache report BYPASS consistently. Oversized files used to report MISS forever, which was indistinguishable from a broken cache. Older write-ups still describe the previous behaviour, and hit-rate figures from before and after the change are not comparable.

Repeated MISS: the cache key keeps changing

A first MISS in each data center is normal — that request is what populates the cache. A URL that misses every time is a different symptom, and the cause is usually that no two requests share a cache key. The key is built from the origin scheme, host, path, and query string, so:

  • Query strings fragment the cache. Every distinct query string is its own entry. Campaign tags, session identifiers, and cache-busting timestamps turn one asset into thousands of entries that are each requested once. Cache Rules can ignore or allow-list specific parameters; sorting them only helps when the same parameters arrive in a different order.
  • Custom keys can shard. Adding a cookie or header that varies per user to the key does exactly what you asked and gives every user a private copy.
  • Vary multiplies entries. A high-cardinality header listed in the origin’s Vary stores a separate variant per value, and the hit rate collapses.
  • Low-traffic assets are evicted. If two consecutive requests to the same data center both miss, Tiered Cache is the usual answer for long-tail content.

Before concluding the cache is broken, confirm both test requests reached the same location: the last three characters of the cf-ray header identify the data center. Note also that the scheme in the key is the one used to reach your origin, not the one the visitor used, which is why changing the encryption mode forces the cache to warm up again.

Making pages cacheable on purpose

  1. Add a Cache Rule matching the paths you want cached, with eligibility set to yes and an explicit edge TTL. The TTL matters: without one, the response falls back to whatever your origin’s headers say.
  2. Exclude anything personalised — carts, dashboards, admin paths, anything gated by a session cookie — in the same rule expression rather than in a second rule bolted on afterwards.
  3. Decide what should happen to Set-Cookie. An edge TTL that ignores origin cache-control will strip it and cache the response, which is correct for anonymous pages and actively dangerous for personalised ones.
  4. Request the URL twice from the same client and confirm you see HIT with an Age that climbs. A single request cannot tell you anything.

Cache behaviour is revised more often than most Cloudflare settings — the bypass reporting change above landed in 2026 — so before relying on a specific row here, check the current troubleshooting reference.

FAQ

What does cf-cache-status: DYNAMIC mean?

It means Cloudflare decided the request was not eligible for caching before it ever looked in the cache, so the request went straight to your origin. The usual reason is that the file extension is not one Cloudflare caches by default — HTML and JSON are not — and no Cache Rule says otherwise.

What is the difference between DYNAMIC and BYPASS?

Timing. DYNAMIC is decided at request time, from the URL and your rules, before the cache is consulted at all. BYPASS is decided at response time: the request was eligible, but something your origin returned — a Set-Cookie header, a no-store directive, an oversized body — made the response unstorable. DYNAMIC is usually fixed in Cloudflare, BYPASS on your server.

What is the difference between BYPASS and MISS in Cloudflare?

BYPASS means Cloudflare refused to store the response. MISS means the response was cacheable and simply was not in that data center's cache yet, so the next request to the same location should be a HIT. A MISS that never becomes a HIT is a third problem, and it is almost always the cache key.

Why is Cloudflare not caching my site?

Most likely because nothing is broken. Cloudflare judges eligibility by file extension, and HTML is deliberately excluded from the default list, so pages are served from your origin and report DYNAMIC until you add a Cache Rule that marks them eligible for cache and gives them an edge TTL.

Why does Cloudflare keep returning MISS on every request?

Almost always because each request produces a different cache key. Query strings count toward the key by default, so tracking parameters, session IDs, or timestamps split one asset into thousands of entries that are never requested twice. Low-traffic assets can also be evicted between requests.

Manage this from your phone

Orange Cloud is a native iOS and Android client for Cloudflare. Sign in with Cloudflare OAuth and flip proxy status, edit DNS records, and read traffic analytics from anywhere.

Get Orange Cloud