Why is a CDN still slow or stale? Cache keys, TTL, ETag, and release design
Diagnose old content and slow requests across browser, CDN, and origin caches. Five tips connect cache keys, max-age versus s-maxage, 304 revalidation, and releasing content-hashed assets on a public site.
Why can a site remain slow or stale after adding a CDN?
First establish whether the slow request used a cached response. A CDN stores copies at edge locations. A hit can reuse a copy; a miss continues to an upstream cache or the origin. A hit in one region does not establish the cache state in another.
Browser copy → CDN edge copy → Upstream cache, if configured → Current origin content
Separate hits, misses, and revalidation
Inspect the URL, response headers, and timings in developer tools. Correlate the provider’s cache-status header with origin logs and measure regional TTFB, or time to first byte. Measurements with browser caching disabled do not reproduce a returning visitor using an old fresh copy.
| Observation | Possible path | Next check |
|---|---|---|
| Only misses are slow | Waiting for upstream fetch or origin work | Origin time, location, key fragmentation |
| Frequent revalidation | Checking an expired copy upstream | Freshness, validators, validation cost |
| Only some visitors see old code | Old browser copy or entry HTML | Actual HTML references and asset filenames |
Cache keys: share only equivalent responses
A 320px image and a 640px image of product 42 differ. If changing only utm_source leaves the body identical, that tracking value can be excluded. Forwarding a value to the origin and including it in the cache key may be separate settings. Preserve distinctions for headers that change language or format too.
/image?id=42&width=320&utm_source=mail -> key=(42,320)
/image?id=42&width=320&utm_source=ad -> key=(42,320)
/image?id=42&width=640&utm_source=mail -> key=(42,640)Do not declare personalized responses safe merely by adding a user ID to the key and marking them public. This case avoids shared storage for member pages and checks authorization and cache policy separately. Verify that image sizes and languages cannot mix before tuning hit rate.
Distinguish max-age, s-maxage, and ETag
Cache-Control: public, max-age=0, s-maxage=300
ETag: "directory-v7"RFC 9111 makes s-maxage override freshness for shared caches. Here the browser checks before reuse, while the CDN may answer from a fresh copy. With Age 240 seconds, about 60 seconds remain; browser validation does not necessarily reach the origin. Check minimum TTLs, overriding rules, and cache eligibility in the actual configuration.
An expired copy can be checked with If-None-Match and reused after a 304 if unchanged. For 2MB JSON this avoids refetching the body from the origin, but retains the round trip and validation work. CloudFront documents constraints on conditional requests when forwarding cookies; the presence of an ETag alone does not establish the expected behavior.
Still stale after purge: fix asset names and release order
Overwriting /app.js does not invalidate fresh browser copies when the CDN is purged. Publish a content-hashed URL such as /app.c3d4.js, then update entry HTML to reference it. Stale HTML still points to old assets, so design entry-page revalidation and acceptable shared-cache staleness too.
- Publish new JS, CSS, and images and verify availability; treat their URLs as immutable.
- Switch entry HTML references and, if needed, verify completion of CDN invalidation for HTML.
- Retain old files while old pages or open tabs may reference them.
- Verify HTML references and running versions for new and returning visitors across regions.
Verify performance, correctness, and update propagation
Compare regional hit and miss timings, origin requests, body bytes, and errors. Products classify revalidated requests differently, so align metric definitions. Increasing freshness to 300 seconds is not a promise of current content; validate accepted staleness and actual propagation against user requirements.
Primary sources
The official sources below were checked on October 27, 2026. Scenarios and numerical examples are illustrative assumptions. Configuration examples are explanatory; they have not been run against live services or benchmarked.
- CloudFront — Delivery path
- Cloudflare — Cache keys
- RFC 9111 — HTTP Caching
- CloudFront — Conditional GET
- Cloudflare — Browser Cache TTL
- CloudFront — Invalidation and versioned names
Tips this post digs into
- A CDN serves from nearby locations; a cache miss still needs an upstream fetch
- Keep response-changing inputs in the CDN cache key; remove irrelevant ones
- Use max-age and s-maxage to separate browser and CDN freshness
- ETag and 304 reduce body retransfers; origin validation still happens
- A CDN purge does not clear browser copies; update static assets with new URLs