CloudFront cache warming: 600+ POPs, regional edge caches, and how to fill them
CloudFront is the largest CDN by location count — 600+ edge POPs feeding from 13 regional edge caches (RECs). That two-tier design is great news for warming: it means a warming strategy gets leverage. It's also the CDN where HTML caching is most often accidentally disabled, so let's start there.
First: is your HTML cacheable at all?
CloudFront honors your origin's Cache-Control. The most common finding
when we point the cache checker at a CloudFront site:
x-cache: Miss from cloudfront on every request, forever — because the
origin sends no-cache, private, or nothing at all while the
distribution uses the CachingDisabled policy for dynamic behavior. Fix the
policy first (use CachingOptimized or a custom cache policy with a real
TTL for anonymous HTML), or warming has nothing to warm. The headers to read:
x-cache (Hit from cloudfront / Miss from
cloudfront) and x-amz-cf-pop (which POP answered, e.g.
FRA56-P5).
The two-tier cache: why warming CloudFront pays twice
When an edge POP misses, it doesn't go straight to your origin — it asks its regional edge cache first. RECs are bigger, fewer (13 worldwide) and hold objects longer than edge POPs. For warming this is a gift:
- Warming one URL from one location per region fills that region's REC.
- Every other POP in the region that misses later gets a fast regional answer (tens of ms) instead of a full origin round trip (hundreds of ms).
So even a modest multi-region warming pass — one request per continent-region — upgrades the "worst case" for your whole audience from origin-latency to REC-latency. A denser pass (one request per country) then fills the actual edge POPs your visitors touch. That's the model warmup.rocks uses: requests from 40+ countries land at the POPs real traffic lands at, and the RECs come along for free.
Invalidations: the cold-cache generator
Every deploy that runs aws cloudfront create-invalidation --paths "/*"
empties all 600+ POPs and all 13 RECs at once. Two things to know (details in
AWS's
invalidation docs):
- Cost: 1,000 invalidation paths per month are free, after that
$0.005 per path — but
/*counts as one path, which is why everybody uses it, which is why whole sites go globally cold on every release. - Better: use versioned asset filenames (cache forever, never invalidate) and invalidate only the HTML paths that changed. And whatever you invalidate — re-warm it right after. A post-deploy warming trigger (e.g. our deploy hook / GitHub Action) closes the gap between "cache empty" and "first visitor" to a couple of minutes.
Origin Shield: one more layer, same story
CloudFront's Origin Shield (an extra REC acting as the single funnel to your origin) further concentrates origin fetches — with shield enabled, a global warming pass typically costs your origin a single-digit number of requests per URL. If your origin is a slow CMS or a Lambda@Edge/SSR function, that's the difference between a warming pass being free and being a load test.
Practical CloudFront warming setup
- Cache policy: real TTLs for anonymous HTML; exclude cookies/headers you don't vary on (every extra cache key dimension multiplies the objects to warm).
- Versioned assets + targeted invalidations instead of
/*where possible. - Origin Shield on if your origin is slow or expensive.
- Scheduled multi-location warming matched to your TTLs, plus a deploy-hook triggered run after each invalidation.
- Verify per location: the global TTFB test shows the REC effect directly — warm regions answer in <100 ms, cold ones show your full origin round trip.
Invalidate on deploy — warm before the first visitor
warmup.rocks warms your CloudFront distribution from 90+ locations on a schedule and on every deploy via hook. CloudFront is auto-detected (x-cache / x-amz-cf-pop), hit ratio reported per location.
Start your 7-day free trial