Cache Miss Types
A cache miss is a request an edge node cannot answer from its own cache. Practitioners name four causes: never fetched (cold/compulsory), evicted for space (capacity), purged or expired (invalidation), or cached at another PoP (fragmentation). Each needs a different fix.
Full Explanation
A cache miss is a request that a CDN edge node cannot answer from its own cache store. The edge then forwards the request toward the origin to fetch the object. A miss is not an error and not a fault report. HTTP defines only the conditions under which a cache may reuse a stored response. HTTP never says why a copy is absent. So “miss type” is a diagnostic vocabulary. Practitioners layer this vocabulary on top of the protocol. What matters is the cause: each cause has its own fix. There are four causes: cold (also called compulsory), capacity, invalidation and fragmentation. Together, they drag your cache hit ratio down. Two of them mean the copy was never at this edge. Two mean it was there and is gone.
How it works
A cache may only answer from a stored response under certain conditions. The response’s target URI and nominated request header fields must match the request. The response must also be fresh, allowed to be served stale, or successfully revalidated (RFC 9111, Section 4). Each miss type names a different way that stored response came to be missing or unusable.
- Cold miss (compulsory miss): the edge has never fetched this URL, so there is nothing to serve. The first request goes to the origin and populates the cache. Cold misses are unavoidable for genuinely new content. Every edge also presents a cold cache again after a node restart, a new PoP deployment, or a purge of everything. Cache warming pre-fetches the hot objects into edge caches before users ask for them. It moves the first fetch off a real visitor, but it cannot remove it.
- Capacity miss: the object was cached but was evicted to make room once the edge’s store filled up. A replacement policy decides which entry to drop. The common policy is LRU (least recently used). It replaces the entry that was accessed less recently than any other. More sophisticated policies also weigh how frequently an entry is used. Capacity misses rise when the working set of traffic is larger than the cache. That is why they bite long-tail content first. An object only a few people request gets evicted before the next request for it arrives.
- Invalidation miss: the cached copy is gone because it was removed explicitly by a purge, or because its freshness lifetime ran out and the copy became stale. A response’s max-age directive says the response is to be considered stale once its age exceeds that many seconds. Either way the next request must fetch again. TTL is the lever. A short TTL causes repeated expiry misses. A long TTL keeps a copy nominally fresh long after the origin changed. Some vendor taxonomies split this into two types. They use “invalidation” for the purge and “expiration” for the elapsed TTL.
- Fragmentation miss: a copy exists somewhere in the CDN but not at the point of presence (PoP) that received this request. Each PoP keeps its own store. So a user routed to Frankfurt can miss an object that is cached in London, even though the network as a whole holds it. Cloudflare states the same behaviour plainly. If requests reach different data centers, each produces its own first-request MISS. Origin shielding, tiered caching and multi-PoP warming are the remedies.
The split is what makes the taxonomy useful. Cold and fragmentation misses are about a copy that was never at this edge. The fix is to put one there: warming, shielding, or a tier behind the edge. Capacity and invalidation misses are about a copy that was there and went away. The fix is to keep it longer: more storage, a policy that fits the traffic, or a longer and more surgical TTL.
Why it matters for a CDN
A miss is the expensive path through a CDN. The edge has to make the full round trip to the origin. This adds latency. It puts one more request on your origin servers. It also burns bandwidth and egress cost that a hit would have avoided. The origin load does not grow gently as the hit ratio slips. Take 10,000 views: a 98% hit ratio sends 200 requests to the origin. A 90% ratio sends 1,000 requests, for the same traffic. That is five times the origin load. Misses also arrive in clusters rather than evenly, because the events that cause them are global. A deploy or a full purge makes every edge cold at once. A traffic spike on an object that is not in cache can turn into a cache stampede against the origin. Four causes demand four different fixes, so there is no single knob to turn. You must identify the type first. Hit ratio is the number operators watch. It is the percentage of accesses that result in cache hits. But it only tells you how big the problem is. It never tells you which of the four causes it is.
What CDNs do
Vendors attack cache misses from two directions. First, a cache-status header lets you tell the possible outcomes apart. Second, extra cache layers plus request collapsing keep a miss at one edge from becoming a request to your origin.
- Cloudflare returns
CF-Cache-Status. This header separates several values.MISSmeans eligible for cache but not present at request time, so served from origin.EXPIREDmeans found in cache but expired, served from origin.STALEmeans expired, served from cache because the origin could not be contacted.BYPASSmeans eligible at request time, but the origin response was ultimately not cacheable.DYNAMICmeans judged not eligible at request time, so the request never looked in the cache. Cloudflare documents that aMISSon the first request in each data center is expected, because that request populates the cache. - Fastly appends X-Cache to all responses by default. It is a deliberately simplified derivative of the internal
fastly_info.statevariable. Anything that is notHITor aHIT-prefixed state is reported asMISS. A PASS is also reported asMISS. Hits that come from serving stale or from a background revalidation are reported asHIT. It tells you hit or not. It does not tell you which miss type you have. - KeyCDN publishes six statuses:
HIT,MISS,EXPIRED,REVALIDATED,UPDATINGandSTALE. An expired object can be served anyway, because the origin is unreachable, the connection timed out, or stale-while-revalidate is in play. In that case it appears asSTALErather than as a plain miss. - Tiered caching puts another cache between the edges and the origin. This converts fragmentation and long-tail capacity misses into upper-tier hits. Cloudflare’s Tiered Cache divides its data centers into lower and upper tiers. A lower tier that misses asks an upper tier. Only an upper tier may ask the origin. Tiered Cache itself is available on every plan. The Generic Global and Regional topologies are Enterprise-only. Fastly’s shielding is selected per origin server in CDN services. It funnels requests from across the global network through one designated shield POP instead. Fastly documents the effect as improving cache hit ratio, “albeit potentially not from the first POP which handles the request”.
- Request collapsing stops one cold object from generating many origin fetches. A cache may use a stored or storable response to satisfy multiple requests. This lets it combine several concurrent requests into a single forward request upon a miss. That reduces load on the origin and the network (RFC 9111, Section 4). CDNs ship this as request coalescing. The same section warns of the limit. If the returned response cannot be used for some of the collapsed requests, the cache still has to forward them. That adds latency.
Watch out for
- A miss is not always labelled
MISS. Cloudflare reservesMISSfor cacheable responses that were simply not in cache. For responses it chooses not to cache, Cloudflare returnsBYPASS, notMISS. The Age header is a second tell. Cloudflare only sends it on responses served from cache. So it is absent on a miss. Read the actual status value before concluding the cache is broken. - Vendor status vocabularies are not comparable. Cloudflare and KeyCDN give expired-but-served content its own values (
STALE,UPDATING). Fastly’s X-Cache reports that same event asHITinstead. Never compare a raw miss count across two CDNs without checking what each one counts. - Repeated misses from the same edge are a capacity story, not a fragmentation one. Cloudflare’s own diagnostic is to compare the last three characters of the
cf-rayheader. This confirms whether two responses came from the same data center. When they did, the usual cause is a low-traffic asset evicted before the next request arrived. The documented answer is Tiered Cache or Cache Reserve, to hold long-tail content longer. - “Fragmentation” in most CDN writing means something else. It means cache key fragmentation. Query strings, cookies or Vary headers split one logical resource into many entries. Each entry misses until it is populated individually. The fix is different: normalise the key. It also hides from naive testing, because two identical test requests produce the same cache key and cannot reveal variance.
- Some misses are the origin’s instruction rather than the CDN’s failure. The
no-storedirective forbids a cache from storing any part of the request or response. An unqualifiedprivatedirective forbids a shared cache from storing the response at all. Fix the Cache-Control header at the origin, not the CDN configuration. - Do not import the CPU-cache taxonomy wholesale. Conflict misses happen when several blocks compete for the same slot because of limited associativity. They are a hardware-cache phenomenon and are not typical in content delivery networks.
- Chasing zero misses is the wrong target. Some misses are unavoidable. Some content must never be cached. The goal is to make misses rare and cheap, not to eliminate them.
Best practice
- Classify before you fix. Read the cache-status header. Check whether an Age header is present. Confirm from the vendor’s request-ID header which edge answered. Only then choose the remedy: warming for cold, storage or replacement policy for capacity, TTL discipline for invalidation, shielding or tiering for fragmentation.
- Set TTLs intentionally. Use long lifetimes for versioned static assets. Use short ones only where the content genuinely changes often. Never leave a framework default unreviewed.
- Measure hit ratio per content type, rather than as one site-wide average. That way a healthy asset class cannot hide a badly performing page behind a blended figure.
- Purge surgically: only the URLs that actually changed. Warm the hot URLs straight after a deploy. Do not flush the whole zone and let the next wave of visitors absorb the cold misses.
- Reach for shielding or tiered caching before buying more edge storage. It attacks fragmentation and long-tail capacity misses at the same time. It needs no change to your TTLs or cache keys.
- Keep genuinely dynamic or personalised content uncached. A slightly lower hit ratio with correct data beats a near-perfect one that occasionally serves one user another user’s page.
Interactive Animation
Examples
Read the CDN response headers to see the miss type:
# Check cache status headers to identify miss types
curl -sI https://cdn.example.com/style.css | grep -i x-cache
# X-Cache: MISS (could be any type)
# X-Cache: HIT (content was cached at this PoP)
# After a purge (invalidation miss)
curl -sI https://cdn.example.com/style.css
# X-Cache: MISS
# Age: 0 (freshly fetched)
# After cache fills up again (subsequent request)
curl -sI https://cdn.example.com/style.css
# X-Cache: HIT
# Age: 45 (cached 45 seconds ago)
Miss Type Cause Fix
Cold Never cached here Cache warming, pre-fetching
Capacity Cache full, evicted Bigger cache, better eviction
Invalidation TTL expired or purged Tune TTLs, stale-while-revalidate
Fragmentation Cached at wrong PoP Origin shield, multi-PoP warming
Frequently Asked Questions
A cache miss is a request an edge node cannot answer from its own cache. Practitioners name four causes: never fetched (cold/compulsory), evicted for space (capacity), purged or expired (invalidation), or cached at another PoP (fragmentation). Each needs a different fix.
Read the CDN response headers to see the miss type:
# Check cache status headers to identify miss types
curl -sI https://cdn.example.com/style.css | grep -i x-cache
# X-Cache: MISS (could be any type)
# X-Cache: HIT (content was cached at this PoP)
# After a purge (invalidation miss)
curl -sI https://cdn.example.com/style.css
# X-Cache: MISS
# Age: 0 (freshly fetched)
# After cache fills up again (subsequent request)
curl -sI https://cdn.example.com/style.css
# X-Cache: HIT
# Age: 45 (cached 45 seconds ago)
Miss Type Cause Fix
Cold Never cached here Cache warming, pre-fetching
Capacity Cache full, evicted Bigger cache, better eviction
Invalidation TTL expired or purged Tune TTLs, stale-while-revalidate
Fragmentation Cached at wrong PoP Origin shield, multi-PoP warming