DEV Community

Cover image for Your CDN cache is colder than you think (and your users are paying for it)
Mohd Anas
Mohd Anas Subscriber

Posted on

Your CDN cache is colder than you think (and your users are paying for it)

There's a comfortable lie most of us tell ourselves after putting a site behind a CDN: it's cached now, it's fast.

It isn't. Not everywhere. Not for everyone.

A CDN is not one cache. It's hundreds of independent ones that don't talk to each other. Cloudflare alone runs 300+ data centers, and each of them keeps its own memory of your site. Fill the cache in Frankfurt and Frankfurt gets a fast site. Tokyo still knows nothing about you. The first visitor there pays the full price: origin render, database, the whole round trip.

I got tired of this, so I built warmup.rocks. This post is the reasoning behind it — the part I'd want to read before trusting a tool with my TTFB.


The number that made me care

I measured it instead of arguing about it. Across 408,000 CDN requests, a cache miss came back 3.5× slower than a hit.

That ratio is roughly what it looks like on a real page load:

Cold cache vs warm cache: 3.2s vs 89ms

3.2 seconds versus 89 milliseconds. Same site, same code, same CDN config. The only variable is whether the edge that visitor landed on had ever seen the page before.

And here's the part that stings: this isn't a rare edge case. It happens to:

  • Every new visitor region. Your first user in São Paulo, Mumbai, or Sydney is always the unlucky one.
  • Every deploy. CI purges the cache, and all 300 caches go cold simultaneously. You just reset the clock for the entire planet.
  • Every long-tail page. Your homepage stays warm from real traffic. That blog post from March, the pricing page in a secondary language, product #4,721 — those expire and nobody re-fills them until a human waits for it.

Your monitoring won't tell you either, because your monitoring probably runs from one region and it's the region that's already warm.


Why "just ping the URL" doesn't work

The obvious fix is a cron job that curls your sitemap. I tried that. It's better than nothing and worse than it sounds.

A curl from your CI runner enters the CDN at whatever edge is closest to that runner. You warm one of several hundred caches and feel productive. The Tokyo user is still waiting.

To actually warm a CDN you have to make requests from where your users are. Geography is the mechanism, not a nice-to-have. That's the whole design constraint.


What warmup.rocks actually does

On a schedule, it requests your pages from 42 countries across 6 continents. Each request enters the CDN in a different region and fills the edge it lands in. That's 90+ edge locations warmed per pass.

Then — and this is the part I care about most — it checks whether it worked.

Warming without verification is just hoping.

Every response's cache status header is recorded: cf-cache-status, x-cache, and the equivalents from other providers. HIT, MISS, EXPIRED — per URL, per colo, per run.

Dashboard showing 93% hit ratio, 215 URLs warmed at 52 edge locations

That's a real site: 215 URLs, 93% hit ratio over 24 hours, 89,605 of 96,813 requests answered from cache. Not a projection. Not "should be faster now." Measured response headers.

You can also see it geographically, which is where the per-edge nature of CDN caching stops being an abstract idea:

Edge coverage world map with per-colo hit ratios

Green dots are edges above 80% hit ratio, amber ones are still warming up, dot size is request volume. Hover any of them and you get the breakdown — Madrid: 2,144 requests, 92% hits, 252 ms, Cloudflare. Underneath, the same data as a table: IAD 95%, LAX 96%, and so on.

When something is slow in one region and fine everywhere else, you can now see it instead of guessing.


Adaptive warming: stop re-fetching what's already warm

Naive warming re-requests every URL on every run. That's wasteful for you and rude to your origin.

So the system learns each URL's actual edge TTL from the observed cache statuses, and moves stable pages onto their own schedule — re-warmed just before they expire, rather than every pass.

Adaptive warming is learning this site's edge TTLs.
36 of 215 URLs now warm on their own learned schedule —
pages that stay cached for days are re-warmed just before
they expire, instead of every run.
Enter fullscreen mode Exit fullscreen mode

Your marketing homepage with a 4-hour TTL and your docs page with a 7-day TTL shouldn't be treated the same way. Now they aren't.


Fitting it into a real stack

Feature grid: sitemap auto-discovery, cache-status analytics, WAF-friendly headers, schedules, CDN auto-detection

A few things that came directly from getting annoyed while building this:

Sitemap auto-discovery. You give it one sitemap URL. Nested sitemap indexes get resolved recursively before every run, so new pages are picked up automatically and deleted pages stop being warmed. The warming list is never stale, and you never maintain a URL list by hand.

CDN auto-detection. Cloudflare, Fastly, CloudFront, Akamai, bunny.net, CDN77 — the provider is identified from response headers, and hit ratio and edge location are reported per provider. Nothing to install, no plugin, no DNS change.

Deploy hooks + an official GitHub Action. The moment your CI purges the cache is the moment you most need warming. Hitting a deploy hook at the end of your pipeline is the whole integration:

# after your deploy + purge step
- name: Re-warm the cache
  run: curl -fsS -X POST "$WARMUP_DEPLOY_HOOK"
  env:
    WARMUP_DEPLOY_HOOK: ${{ secrets.WARMUP_DEPLOY_HOOK }}
Enter fullscreen mode Exit fullscreen mode

(There's a proper GitHub Action too if you'd rather not shell out — check the docs for the current inputs.)

WAF-friendly warm headers. Bot protection is supposed to block traffic like this. So you can set a custom secret header sent with every warming request, allowlist that one value, and let us through — and only us.

x-warm-secret: ••••••••
Enter fullscreen mode Exit fullscreen mode

And if a WAF rule does start eating warming runs, you get an email instead of silently drifting back to a cold cache for three weeks.

Mobile cache variant. Many setups cache desktop and mobile separately. A second pass with a mobile user agent fills that variant too, otherwise you've warmed half your traffic.


Verify it yourself first

You don't need to take any of this on faith, and you don't need an account. Two free tools, no signup:

  • a multi-location cache checker — run your URL and see where it's HIT and where it's MISS right now
  • a website speed test

Honestly, start there. Check a page you know is cached, from a region you don't live in. That result is the entire argument for this product, and it's more convincing coming from your own site than from my screenshots.


Why TTFB is worth this much trouble

TTFB is the floor under everything else. LCP can't be fast if the first byte is slow — no amount of image optimization, critical CSS, or font preloading buys back a 3-second origin round trip. You're optimizing the visible part of the page while the invisible part eats your budget.

And Core Web Vitals are measured from real users, in their regions — not from your laptop in the region where your cache happens to be warm.

Cold edges are one of the few remaining perf problems where the fix is boringly mechanical: send requests from the right places, verify the headers came back HIT, repeat before things expire.

That's all this is. It just needed to actually exist.


warmup.rocks — nothing to install, no plugin, no DNS change. Free cache checker and speed test if you just want to see how cold things really are.

Happy to answer anything about the measurement methodology or the adaptive TTL logic in the comments — that's the part I found most interesting to build.

Top comments (0)