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, because every warming request does double duty. 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