Why Is Cloudflare Not Caching My Site?

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

Updated August 20, 2026 · 8 min read

Cloudflare caches by file extension, and HTML and JSON are not on the default list — so most pages return cf-cache-status: DYNAMIC until a Cache Rule makes them eligible. BYPASS means the origin response blocked caching instead.

The common assumption is that traffic routed through Cloudflare is cached traffic, and that a slow page means the cache is broken. Neither is quite right. Static assets are cached by default, pages are not, and there are several distinct ways a response can end up served from your server — each with a different fix. Guessing between them is how people end up stacking rules that cancel each other out.

Read the header before you change any setting

Every proxied response carries a cf-cache-status header, and it names the decision that was made. Fetch the URL and look at it before touching the dashboard:

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

The Age header is a useful companion: it reports how many seconds the asset has been sitting in cache, and it appears only on responses actually served from cache. If Age is missing, the response came from your origin, whatever else the page appears to be doing. If the headers are absent entirely, the hostname is probably not proxied at all — see what the orange cloud means in Cloudflare 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. The full reference lives in the cache responses documentation.

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 BYPASSboth 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 DYNAMICfor 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
Body over the size limit512 MB on Free, Pro, and Business; 5 GB by default on Enterprise
Request had AuthorizationCacheable only if the response also carries public, s-maxage, or must-revalidate

Two of these are worth knowing precisely. First, 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. Second, 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.

Why is Cloudflare not caching my HTML pages?

Because Cloudflare decides eligibility by file extension, and HTML is deliberately excluded from the default list. Pages are served from your origin until you add a Cache Rule that marks them eligible for cache and gives them an edge TTL.

What is the difference between BYPASS and MISS in Cloudflare?

BYPASS means Cloudflare refused to store the response — it was eligible at request time, but something in the response, such as a Set-Cookie header or an oversized body, made it uncacheable. MISS means the response was cacheable and simply was not in that data center's cache yet, so the next request should be a HIT.

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.

Does a Set-Cookie header stop Cloudflare from caching?

By default, yes — a response carrying Set-Cookie is not stored, and it reports BYPASS. You can override that by setting an explicit edge TTL in a Cache Rule that ignores origin cache-control, by having the origin send Cache-Control: private=“Set-Cookie”, or by stripping the header with a response header Transform Rule.

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