Why My CDN Cache Never Hit: Field Notes on Cache-Control, s-maxage, and stale-while-revalidate
A CDN that reports a miss on every request is almost never misconfigured at the CDN — the response itself is telling it not to cache. The three changes that fixed my hit rate were removing an accidental Set-Cookie from a public GET response, splitting one Cache-Control header into a CDN-Cache-Control plus Cache-Control pair so the edge and the browser could hold different lifetimes, and…
When a CDN reports a miss on every request, it is almost always due to the response not being cacheable, rather than being misconfigured at the CDN level. Three common fixes for this issue are:
1. Remove any accidental Set-Cookie headers from public GET responses, as these prevent the cache from storing the response.
2. Split Cache-Control headers into separate CDN-Cache-Control and Cache-Control headers, allowing the edge and browser to have different lifetimes for the content.
3. Add the stale-while-revalidate directive to allow the cache to serve an expired copy for a short period while refreshing it in the background.
The response headers that indicate why a request missed are x-vercel-cache (Vercel), cf-cache-status (Cloudflare), and the standard Cache-Status header defined in RFC 9211. The MISS and BYPASS status codes have distinct meanings: MISS means the cache looked but found nothing, while BYPASS means the cache refused to store the response altogether.
max-age=N determines the freshness period for a response in every cache, including the browser's private cache. s-maxage=N overrides max-age only in shared caches, while browsers ignore it. stale-while-revalidate=N specifies a grace period after expiry during which the cache can serve the stale copy immediately and refresh it in the background.
To ensure a response is never written to disk, use no-store as the Cache-Control directive. The Cache-Control headers can have different effects on the browser and CDN: public, max-age=0, s-maxage=300, and stale-while-revalidate=86400 instructs the browser to revalidate on every request, while the edge treats the copy as fresh for five minutes and serves it instantly for a day.
If the same response is needed by both the browser and CDN with different lifetimes, use targeted headers like Vercel-CDN-Cache-Control or CDN-Cache-Control to specify separate lifetimes for each. A Set-Cookie header on the response will silently disable CDN caching, as shared caches do not store this field per RFC 9111. The Vary header can add request headers to the cache key, creating a unique entry for each distinct value and causing hit rates to drop.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.
This story
This is one outlet's version. Read the fullest account.