Cache Hit Ratio (CHR)

Caching

The share of requests a cache answers from its own stored copies instead of fetching from the origin: hits ÷ (hits + misses). A cache hit ratio of 95 percent means 95 requests in 100 never reached the origin. It measures offload and cost, not how close to the user a response was served.

Also known as cache hit rate, hit ratio, hit rate.

9 min read Updated Aug 30, 2026

Full Explanation

The cache hit ratio (CHR) is the share of requests a cache answers from its own stored copies. It does not forward these requests to the origin server. A CHR of 95 percent means 95 requests in 100 never reached your origin. Cloudflare defines it as "a measurement of how many content requests a cache is able to fill successfully, compared to how many requests it receives". Cloudflare also notes it "applies to any cache; it's not just for measuring CDN performance". Still, most CDNs surface it in their dashboard.

It is a calculated figure, not a protocol feature. No response header sets it, and no cache directive controls it directly. It is also not a measure of how close to the user a response was served. Most CDNs count any response from any of their caches as a hit. So a high ratio can coexist with content being served from deep inside the network. Cloudflare's own guidance is blunt: "Cache hit ratio is not the last word in CDN performance."

How it works

A cache hit is a request the cache fulfils from a stored copy. A miss is a request the cache cannot fulfil: it does not hold the content, so it passes the request to the origin. It then stores the response, so the next request hits (see cache miss types). The ratio is hits ÷ (hits + misses) over a period. It is usually shown as a percentage: 39 hits and 2 misses gives 39 ÷ 41 = 0.951, or 95.1 percent.

There are two ways to count, and they answer different questions. A request-based ratio counts each request once. It tracks performance, because every miss means a trip to the origin that adds latency. A byte- or data-transfer-based ratio counts bytes instead. It tracks cost, because providers bill egress bandwidth. Web-caching research has used both since the 1990s, defining hit ratio as requests that hit as a percentage of total requests, and byte hit ratio as bytes that hit as a percentage of total bytes requested. The two figures come apart whenever the objects that miss differ in size from the objects that hit. A policy that values every object equally, regardless of size, can win on request hit ratio while losing on byte hit ratio.

A ratio falls when something stops a request from reusing a stored copy:

  • a cache key that varies per request, for example query strings that only feed analytics, because each distinct key is a separate cache entry;
  • a Vary header naming a high-cardinality request header. Fastly warns that Vary "is frequently used incorrectly, which can lead to abysmal hit ratios". It found "just shy of 8,000" distinct User-Agent strings in a sample of 100,000 requests. Each one is a separate stored variant unless the header is normalised first;
  • TTL values so short that the stored copy expires before the next request arrives and has to be fetched again;
  • responses that are not eligible for caching at all, because Cache-Control forbids it, or because the content type is uncached by default. HTML is the common case.

Why it matters for a CDN

Every miss is a request the CDN forwards to your origin. That costs the user latency and your servers capacity. It also costs you egress bytes, since most hosting providers charge for data sent from their servers. A higher ratio is how a CDN buys back all three at once. Cloudflare puts it as "faster responses for visitors and less traffic to your origin". Fastly notes that keeping traffic off the origin "translates directly to processing and infrastructure offload, which can lead to great cost savings".

But the number is an average over whatever traffic you happened to serve. So there is no universal target. Cloudflare observes that a mostly static site "could easily have a cache hit ratio in the 95-99% range". It also says "a website with lots of dynamic content may have a much lower cache hit ratio". Fastly is more direct: "A good CDN cache ratio score depends on the website, and a higher score does not necessarily mean 'better'." Its documentation still gives a working floor: "Aim for a 90%+ cache hit ratio". Fastly treats anything lower as a prompt to investigate Cache-Control headers, cache key fragmentation, short TTLs and unexpected traffic.

What CDNs do

  • Cloudflare. Cache Analytics shows how much traffic is served from Cloudflare's cache versus the origin. It has a Requests view (the default, for performance) and a Data Transfer view (for cost analysis). It is not a universal feature. It is unavailable on the Free plan. Retention runs from 7 days on Pro to 30 days on Business and Enterprise. To lift a ratio dragged down by misses, Cloudflare suggests enabling Tiered Cache. It also suggests creating a custom cache key "so that multiple URLs match the same cached resource, for example by ignoring the query string".
  • Fastly. The Service Overview dashboard reports Hit Ratio ("the percentage of cache hits to all cacheable content over time"). Under backend load it reports Origin Offload ("the percentage of bytes served to end users that were cached by Fastly"). This gives a request-based and a byte-based view side by side. Edge Hit Ratio and Media Shield Hit Ratio are separate metrics. They appear in the Media Shield collection, not on the general cache panel. Fastly's default cache key is the URL plus the Host header. Its advice is to leave it alone: "Adding too much information reduces your cache hit ratio, while adding too little can cause caching across security domains."
  • AWS CloudFront. It exposes a CloudWatch CacheHitRate metric (valid statistic Average, unit Percent), defined as "the percentage of all cacheable requests for which CloudFront served the content from its cache". It is opt-in: "To get this metric, you must first turn on additional metrics."
  • Varnish. The MAIN counter group exposes cache_hit ("an object has been delivered to a client without fetching it from a backend server"). It also exposes cache_miss ("the object was fetched from the backend before delivering it to the client"). You combine these yourself. Read the neighbouring counters too. Hits served under grace are counted in cache_hit even though the object had expired. The counters cache_hitpass and cache_hitmiss are tracked separately.

Watch out for

  • A hit is not necessarily a nearby hit. Fastly's own analysis notes that "most CDNs consider any response from one of their caches a 'hit' when they calculate CHR, as long as it didn't make it to origin". So long-tail content can be answered by a distant parent cache and still count.
  • Edge and global ratios answer different questions: "CHRedge is a performance metric and CHRglobal is one for offload." With a shield in front of the origin, some requests are counted at both layers. That makes a single reported ratio a hybrid of the two.
  • Revalidation is counted inconsistently. On Cloudflare, a revalidated request counts as Served by Cloudflare in the Data Transfer view, because the body came from cache. But it counts as Served by Origin in the Requests view, because the origin was still contacted. Fastly notes that a 304 from origin "will imply a cache hit" under regular conditions. But it may land as a miss when Image Optimizer or segmented caching is in play.
  • The denominator is vendor-specific, so ratios are not comparable across providers. CloudFront's CacheHitRate counts cacheable requests only: "HTTP POST and PUT requests, and errors, are not considered cacheable requests". Fastly likewise excludes passes: "We want to exclude PASSes in our calculation since they're not cacheable objects to start with." Cloudflare does the opposite. It reports uncacheable responses as Served by Origin, and lists that Dynamic status as something to fix to improve your ratio. Where uncacheable traffic is excluded, a near-perfect ratio can sit alongside a great deal of real origin traffic.
  • Dashboard figures may be sampled. Cloudflare's Requests summary "is based on a sample of requests, not the full dataset", with totals extrapolated. So treat small differences between reporting periods with care.

Best practice

  • Track the request-based ratio for performance. Track the byte or data-transfer ratio for egress cost. Reporting only one hides half the picture.
  • Diagnose by cache status before changing configuration. Cloudflare's statuses map to distinct fixes. Dynamic means not eligible for caching. Expired means the TTL elapsed before the next request. Revalidated means the origin was consulted to confirm freshness. Miss means it was not in that cache at all.
  • Reduce cache-key fragmentation. Ignore tracking-only query parameters, and normalise a request header down to a handful of values before you Vary on it. But do not strip so much that responses from different security domains collide.
  • Prefer a long cache lifetime plus purging over short TTLs. Fastly's guidance is to set a long lifetime and "send a purge request to clear the old content" when it changes. This raises the ratio without serving content you know to be out of date.
  • Measure per POP and per region, not only globally. Fastly's Historical Stats API takes a datacenter parameter to "limit query to one or more Fastly POPs". So an aggregate need not hide a cold or misconfigured location.
  • Read the ratio alongside user-perceived latency and where content is actually served from. Cloudflare notes that "where the content is served from" matters too, and that a CDN's purpose is speed and reliability overall, not one maximised number.

Interactive Animation

Loading animation...

Examples

# Check cache status in response headers
$ curl -sI https://cdn.example.com/style.css | grep -i 'x-cache\|cf-cache'
X-Cache: HIT
CF-Cache-Status: HIT

# Calculate CHR from CDN logs
# hits / (hits + misses) * 100
$ cat access.log | awk '{print $NF}' | sort | uniq -c
  95234 HIT
   4766 MISS
# CHR = 95234 / 100000 = 95.2%

# Varnish: built-in stats
$ varnishstat -1 | grep cache_hit
cache_hit     95234   47.2 Cache hits

Frequently Asked Questions

The share of requests a cache answers from its own stored copies instead of fetching from the origin: hits ÷ (hits + misses). A cache hit ratio of 95 percent means 95 requests in 100 never reached the origin. It measures offload and cost, not how close to the user a response was served.

# Check cache status in response headers
$ curl -sI https://cdn.example.com/style.css | grep -i 'x-cache\|cf-cache'
X-Cache: HIT
CF-Cache-Status: HIT

# Calculate CHR from CDN logs
# hits / (hits + misses) * 100
$ cat access.log | awk '{print $NF}' | sort | uniq -c
  95234 HIT
   4766 MISS
# CHR = 95234 / 100000 = 95.2%

# Varnish: built-in stats
$ varnishstat -1 | grep cache_hit
cache_hit     95234   47.2 Cache hits

Yes. Cache Hit Ratio is also known as cache hit rate, hit ratio, hit rate. The share of requests a cache answers from its own stored copies instead of fetching from the origin: hits ÷ (hits + misses). A cache hit ratio of 95 percent means 95 requests in 100 never reached the origin. It measures offload and cost, not how close to the user a response was served.

Related CDN concepts include:

  • Cache Fill — A cache fill is the fetch-and-store after a cache miss: the cache pulls the resource …
  • Cache Key — The cache key is the identifier a cache derives from a request to decide which …
  • Cache Miss Types — A cache miss is a request an edge node cannot answer from its own cache. …
  • Origin Shield — A cache tier a CDN places between its edge servers and its origin. Cache misses …
  • Vary Header — Vary is the response header that names the request headers the origin used to select …