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
| Header | Tells you | Note |
|---|---|---|
Age | Seconds since the edge stored the copy | Age: 0 can still be a hit from a shield |
Cache-Control | The policy that was applied | May differ from what the origin sent |
| CDN cache status | Hit, miss, revalidated, dynamic | The header name differs per provider |
Via | Which proxy handled the request | Useful when several layers are involved |
X-Forwarded-For | The client address chain | Only meaningful if the origin trusts it |
Server | Often the CDN, not your server | Absence of it proves nothing |
X-Cache-Hits | How many times this object has been served | A 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
doneA miss debugging workflow
- Request the URL twice and compare. If
Agenever rises, the response is not being stored at all. - Look at the response headers.
no-store,private,Set-Cookie, or anAuthorizationheader in the request will all prevent caching. - Check the cache key. Query parameters, a variation by cookie or a
Varyon a header that changes per request all split the cache. - 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.
- Confirm the origin is not sending
Set-Cookieon static assets. One stray cookie header disables caching on the whole response. - 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"| Observation | Interpretation | Action |
|---|---|---|
Age: 0 on every request | Not cached, or cached for zero seconds | Fix the headers or the cache rule |
Rising Age until the TTL | Working as configured | Consider inverting the TTL to the content's change rate |
| Miss from two locations, hit from one | No shield or an uneven cache | Enable a shield if origin load matters |
| Hit ratio high, speed unchanged | The bottleneck is not the CDN | Profile the origin and the client-side work |
| Hit ratio low but origin load fine | The origin is already fast | Do not optimise what is not a problem |
What to measure
| Metric | Definition | Target |
|---|---|---|
| Cache hit ratio | Hits divided by total requests at the edge | Above 90 percent for static assets |
| Origin offload | Share of requests the origin never sees | The number that matters for cost |
| TTFB at the edge | Time to first byte from the PoP | Low and stable; a spike means a miss or a slow origin |
| LCP and INP by country | Real user experience in the field | Compare regions to spot a routing problem |
| Bytes served | Per asset type | Watch images and video separately |
| Purge count | How often you invalidate | High 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.
Related
Cache hierarchy: browser, edge and origin caches Multi-CDN, failover and cost optimisation
Last refreshed 2026-09-18.