Measuring CDN performance and debugging cache issues

Reading cache status headers, hit ratio and origin offload, TTFB and real user monitoring, and a step-by-step workflow for a suspected cache miss.

Reading the response

HeaderTells youNote
AgeSeconds since the edge stored the copyAge: 0 can still be a hit from a shield
Cache-ControlThe policy that was appliedMay differ from what the origin sent
CDN cache statusHit, miss, revalidated, dynamicThe header name differs per provider
ViaWhich proxy handled the requestUseful when several layers are involved
X-Forwarded-ForThe client address chainOnly meaningful if the origin trusts it
ServerOften the CDN, not your serverAbsence of it proves nothing
X-Cache-HitsHow many times this object has been servedA good sign the cache is working
# one command that shows the whole story
curl -sI https://example.com/static/app.js \
  | grep -iE "age|cache-control|etag|vary|via|server|x-cache|content-encoding"

# and a loop that shows a miss followed by a hit
for i in 1 2 3; do
  curl -sI https://example.com/static/app.js | grep -iE "^age|x-cache"
  sleep 1
done

A miss debugging workflow

  1. Request the URL twice and compare. If Age never rises, the response is not being stored at all.
  2. Look at the response headers. no-store, private, Set-Cookie, or an Authorization header in the request will all prevent caching.
  3. Check the cache key. Query parameters, a variation by cookie or a Vary on a header that changes per request all split the cache.
  4. Ask the edge twice from the same location and once from a different one. A hit in one place and a miss in another is expected without a shield.
  5. Confirm the origin is not sending Set-Cookie on static assets. One stray cookie header disables caching on the whole response.
  6. Check that the object is not too large or too small for the platform: very small objects and very large ones are sometimes not cached.
# does a stray cookie header break caching?
curl -sI https://example.com/static/app.js | grep -i "set-cookie"

# is the cache key split by a query parameter?
curl -sI "https://example.com/static/app.js?utm_source=news" | grep -iE "age|x-cache"
curl -sI "https://example.com/static/app.js" | grep -iE "age|x-cache"

# and from another location, expecting an independent cache
curl -sI https://example.com/static/app.js -H "Host: example.com" --resolve example.com:443:203.0.113.9 \
  | grep -iE "age|x-cache"
ObservationInterpretationAction
Age: 0 on every requestNot cached, or cached for zero secondsFix the headers or the cache rule
Rising Age until the TTLWorking as configuredConsider inverting the TTL to the content's change rate
Miss from two locations, hit from oneNo shield or an uneven cacheEnable a shield if origin load matters
Hit ratio high, speed unchangedThe bottleneck is not the CDNProfile the origin and the client-side work
Hit ratio low but origin load fineThe origin is already fastDo not optimise what is not a problem

What to measure

MetricDefinitionTarget
Cache hit ratioHits divided by total requests at the edgeAbove 90 percent for static assets
Origin offloadShare of requests the origin never seesThe number that matters for cost
TTFB at the edgeTime to first byte from the PoPLow and stable; a spike means a miss or a slow origin
LCP and INP by countryReal user experience in the fieldCompare regions to spot a routing problem
Bytes servedPer asset typeWatch images and video separately
Purge countHow often you invalidateHigh purge rates mean the caching design is wrong
  • Field data beats synthetic tests for anything user-facing. A lab run from one location cannot see a routing problem affecting a whole country.
  • Segment the metrics by country and by device. An average hides the region that is broken.
  • Track the purge count as a health metric. Needing to purge often is a signal that URLs are not versioned.
⚠️
A high hit ratio is not automatically good. If the cache is serving stale content, or the page is cached without varying on the header that changes it, the metric looks excellent while users see the wrong thing. Always pair the ratio with a correctness check.

FAQ

Why is my hit ratio 60 percent on a static site?
Usually the cache key. Tracking parameters, cookies on the request or an over-broad Vary header split the cache into many small entries. Normalise the URL and strip what does not affect the response.
How long should I wait after a purge?
Check with a cache-busting parameter or a different hostname. Purging propagates across points of presence, so a purge that is instant in one location can take minutes elsewhere.

Cache hierarchy: browser, edge and origin caches Multi-CDN, failover and cost optimisation

Last refreshed 2026-09-18.