How to check if CDN cache is hit or miss

Print the headers and keep the CDN's own status line: curl -s -D - -o /dev/null <url> | grep -i -E 'x-cache|cf-cache-status|age'. One request tells you what happened to it. Two requests to the same cold URL tell you whether the second one was served warm.

Why check this

A cache that never warms turns every visitor into an origin request, and the symptom is a slow page with a healthy-looking origin. Run this after a CDN rule change, after adding a query parameter to a URL, and on any release that alters vary or cookies. The failure it catches is a page that reports MISS on every request because a Set-Cookie or a request-specific header makes the response uncacheable, so the origin absorbs all the traffic the CDN was bought to absorb.

Prerequisites

Steps

  1. Step 1.

    Read the status header and age together. A hit that has been stored for a while shows both.

    curl -s -D - -o /dev/null 'https://www.cloudflare.com/' | grep -i -E '^cf-cache-status|^age|^cache-control'
    
    cf-cache-status: HIT
    age: 3
    cache-control: max-age=10

    age: 3 against max-age=10 says the stored copy is 3 seconds old with 7 seconds of freshness left.

  2. Step 2.

    Request a cold URL. An unused query parameter gives the edge a key it has never seen.

    curl -s -D - -o /dev/null 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control?probe=1' | grep -i -E '^x-cache|^x-cache-hits|^age'
    
    age: 0
    x-cache: MISS, MISS, MISS
    x-cache-hits: 0, 0, 0

    Three values, three cache layers, read in request order. Every one of them missed, and age: 0 confirms the response was produced now.

  3. Step 3.

    Repeat the identical request and compare. The layer that stored the response on the way back now answers from storage.

    curl -s -D - -o /dev/null 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control?probe=1' | grep -i -E '^x-cache|^x-cache-hits|^age'
    
    age: 0
    x-cache: MISS, MISS, HIT
    x-cache-hits: 0, 0, 1

    The third position flipped to HIT and its hit counter reads 1. The two layers in front still missed, which is why one word is never the whole answer.

  4. Step 4.

    Check a host you did not expect to be behind a cache. The status header appears wherever an edge is in the path.

    curl -s -D - -o /dev/null 'https://example.com/' | grep -i -E '^cf-cache-status|^age|^server'
    
    server: cloudflare
    age: 7386
    cf-cache-status: HIT

    An age of 7386 seconds on a document with no cache-control header at all. The edge chose that lifetime by heuristic, and nobody configured it.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | HIT with age above 0 | Served from the edge, no origin request | Nothing. Compare age against max-age to see how much freshness is left. | | MISS on the first request, HIT on the second | The cache warms correctly | Working. Record which layer flipped, as in step 3. | | MISS on every request | The response is not being stored | Look at set-cookie, vary and cache-control on the response. Any of the three can block storage. | | A value naming a bypass, such as DYNAMIC | The edge decided this route is not cacheable | Check the CDN rule before the headers. A path rule outranks the origin's directives. | | A value naming a revalidation, such as REVALIDATED | The entry existed and was rechecked with the origin | Cheap if a validator exists, a full transfer if not. Check the ETag. | | age present, status header absent | A cache is in the path and does not name itself | Trust age. It is the standard header, and it is enough to prove storage. |

Common mistakes

Sign: The first request already reports HIT, so the warming test proves nothing.Cause: Somebody else requested that URL first, or a prefetch did. A cold measurement needs a key no one has used, which an unused query parameter provides without touching the origin configuration.
Sign: One header says HIT and the page is still slow.Cause: More than one cache sits in the path. Step 3 returns three values at once, and only the last layer hit. The nearest layer to the user is the one that decides the latency, so read the whole list.
Sign: Every request is a MISS and the cache-control header looks correct.Cause: A Set-Cookie on the response makes most edges refuse to store it, and a broad vary splits one URL into a variant per request header value. Neither shows up in cache-control, so reading that header alone hides the cause.
Sign: The staging edge hits and production misses with the same configuration.Cause: Cache keys are per point of presence. A request routed to a different city meets an empty cache, so the first request from a new region misses by design rather than by fault.

What to check next

FAQ

What is the difference between cache hit and cache miss?

A hit is answered from the cache with no origin request. A miss goes to the origin and is usually stored on the way back, which is why the second identical request in step 3 hits. Both return 200 to the client.

How to check x-cache header?

Filter it out of the response headers, as in step 2. The value can be a list when several caches are chained, read left to right in request order, with x-cache-hits giving the per-layer counters.

How to check cf-cache-status header?

The same command with a different header name, as in step 1. Cloudflare uses cf-cache-status, Fastly and Varnish use x-cache, and other vendors pick their own. Grep for all of them at once.

How to check age header?

age counts the seconds the response has sat in a cache. It is standard, so it appears whatever the vendor. Step 4 shows an age of 7386 on a document whose origin sends no cache-control at all.

Does a HIT mean the content is current?

No. It means the content came from storage. Compare age with max-age to see whether it is still fresh, and read the stale window before assuming an old age is a fault.

Verified

Verified by Maks Vernycurl 8.21.0

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic5 minpublished updated Maks Verny