Cache hit ratio: how to measure it and how to raise it
If you only track one CDN metric, track this one. Cache hit ratio is the share of requests answered from cache instead of your origin — it's simultaneously a speed metric (hits are 10–100× faster), a cost metric (misses cost origin compute and egress) and a resilience metric (a warm cache absorbs traffic spikes your origin can't).
The definition — and its two flavors
Request hit ratio = cache hits / total cacheable requests
Byte hit ratio = bytes served from cache / total bytes served
Request hit ratio tells you how often users get the fast path. Byte hit ratio tells you how much bandwidth your origin is spared — a few large video misses can tank it while the request ratio looks great. For web performance work, the request ratio on HTML documents is the number to obsess over: assets are usually cached well already; HTML is where misses hurt and where most sites do worst.
What counts as a hit?
CDNs report more than HIT and MISS. The statuses worth knowing (Cloudflare vocabulary, other CDNs have equivalents):
| Status | Meaning | Counts as |
|---|---|---|
HIT | Served from edge cache | hit |
MISS | Not in cache, fetched from origin, stored | miss |
EXPIRED | Was cached, TTL ran out, revalidated at origin | miss |
REVALIDATED | Stale entry confirmed still fresh (304) | cheap miss |
DYNAMIC | Cloudflare decided not to cache (default for HTML!) | not cacheable |
BYPASS | Response opted out (no-store, cookies) | not cacheable |
A subtle trap: DYNAMIC requests often aren't counted in the "cached"
denominator at all, so dashboards can show a flattering ratio while every HTML request
goes to origin. Check the ratio for HTML specifically.
What's a good number?
Rules of thumb from CDN vendors and practice: static assets should sit at 95%+; HTML on a well-configured site can reach 85–95%; a byte hit ratio above 90% is healthy. If your HTML hit ratio is below 50%, there's almost always low-hanging fruit — usually one of the seven levers below.
Seven levers to raise it
- Make HTML cacheable at all. Cloudflare doesn't cache HTML by
default. You need a Cache Rule (or
Cloudflare-CDN-Cache-Controlfrom the origin) plus purge-on-publish so freshness doesn't suffer. - Extend TTLs. Every TTL expiry is a future miss. If you can purge on content change, edge TTLs of days are safe — freshness comes from purges, not short TTLs.
- Kill cache-busting variance. Query strings (
?utm_source=…), unnecessaryVaryheaders and per-visitor cookies each multiply cache keys or block caching entirely. Normalize the cache key. - Enable tiered caching. Misses at small edge locations get answered by upper-tier hubs instead of your origin — turning global misses into regional hits.
- Use stale-while-revalidate. Serving a slightly stale copy while refreshing in the background converts would-be misses into instant responses.
- Warm the long tail. Popular pages stay warm organically; the hundreds of pages with a few daily visits each are permanently cold without help. This is where cache warming moves the aggregate number most.
- Re-warm after purges. Deploys and purges legitimately empty the cache. A warming pass right after brings the ratio back in minutes.
Measuring it per edge location
A global hit ratio hides geography. Your top markets keep their colos warm; everywhere
else runs cold — and the global average looks fine. Slice by colo (Cloudflare: the
cf-ray suffix; Fastly: x-served-by; CloudFront:
x-amz-cf-pop) to see where visitors actually get misses. That per-colo view
is also the honest way to judge whether warming works: the ratio should rise in the
locations you warm, run over run.
See your hit ratio per colo, per run
warmup.rocks reports HIT/MISS/EXPIRED per URL and edge location for every warming pass — across Cloudflare, Fastly, Akamai, CloudFront and more.
Start your 7-day free trial