I work on 2-6 projects at any given time. Multiple deploys a day. And after every single one, the same ritual: open Cloudflare, find the zone, purge cache, go back to the site, hard refresh, check the page, move on. Sometimes I’d forget. Sometimes I’d purge, switch to another project, come back five minutes later — stale page. Purge again.
Each cycle: 1-2 minutes. Across a day of shipping: 15-30 minutes of my life, spent clicking “Purge Everything” and waiting for first-hit loads to crawl back.
The worst part wasn’t the time. It was the background anxiety. Knowing that every visitor hitting my site after a deploy was getting a slow, uncached response until someone happened to load that page.
The bug I didn’t catch for weeks
My deploy script had a Cloudflare cache purge step. It ran after every deploy. It printed ✓ Cloudflare cache purged. Green checkmark and everything.
It was purging the wrong site.
The CF_ZONE_ID in my .env pointed to salespathpro.com. A completely different project. Every deploy of launchwithben.com was dutifully clearing the cache of a site I wasn’t even working on. Meanwhile, launchwithben.com’s Cloudflare cache was never being purged at all.
I only caught it when I stopped trusting the output and checked what it actually did:
# What I assumed was happening
curl -s "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['result']['name'])"
# Output: salespathpro.com
# ...not launchwithben.com
The deploy script said “success” because the API call succeeded. It purged a cache. Just not mine. Same class of problem as flying blind with expensive instruments: the tooling works, but it’s pointed at the wrong thing.
The fix that did more than fix
After correcting the zone ID, I could have stopped there. But the manual warm-up problem remained. Every page is slow on its first hit after a purge, until a visitor loads it and primes the CDN cache.
Then the obvious thought: why not hit every page myself, right after purging?
And the follow-up: if I’m already hitting every page, I might as well check the status codes.
One script. Three jobs. Cache purge, cache warm, sitemap QA.
Before: Manual CF purge (sometimes wrong zone), cold pages after deploy, sitemap errors discovered days later by crawlers
After: Automated purge → parallel warm of every sitemap URL → status code report. Zero manual steps. Sitemap QA is free.
The script
50 lines of bash. Runs standalone or as part of deploy.sh.
# Fetch every URL from the sitemap
URLS=$(curl -s "$SITEMAP_URL" | grep '<loc>' | \
sed 's/.*<loc>//;s/<\/loc>.*//')
# Warm in parallel (6 concurrent) and capture status codes
echo "$URLS" | xargs -P 6 -I {} \
curl -s -o /dev/null -w "%{http_code} {}\n" --max-time 10 "{}"
xargs -P 6 is the key. It runs 6 requests in parallel instead of one at a time. 61 URLs warm in about 10 seconds instead of 60+.
Each response gets classified:
The output after a clean deploy:
✓ Cloudflare cache purged
✓ Sitemap clean — 61/61 URLs return 200
And when something’s wrong:
! Sitemap issues — 59 OK, 2 problems out of 61 URLs
⚠ 301 /projects/old-slug — redirect in sitemap (should be final URL)
✗ 404 /learn/deleted-post — dead URL in sitemap
The cache headers
While I was in there, I fixed the cache duration too. The original settings were conservative to the point of uselessness:
| Setting | Before | After |
|---|---|---|
Browser cache (max-age) | 1 day | 7 days |
CDN cache (s-maxage) | 1 hour | 1 day |
| Stale-while-revalidate | 1 day | 7 days |
stale-while-revalidate is the important one. It tells Cloudflare to serve the cached version immediately and refresh in the background. Visitors never see a slow page. They get the stale version instantly while the CDN fetches a fresh one behind the scenes.
With the cache warm running after every deploy, the CDN has a fresh copy of every page. With 7-day stale-while-revalidate, even if the cache expires between deploys, visitors still get instant responses.
The insight
The cache warm and the sitemap QA are the same operation. You’re hitting every URL. The status code is already in the response. Checking it costs exactly zero extra work.
This is the same pattern as conversion tracking that rides on existing analytics calls, or deploy preflights that reuse the test suite. The best monitoring is the kind that piggybacks on something you’re already doing.
- If you do something manually after every deploy, it belongs in your deploy script
- If you’re hitting URLs anyway (warm, verify, smoke test), check the status codes
- If your deploy script says “success,” verify it’s actually doing what you think it is
- If a step takes 1-2 minutes and you deploy multiple times a day, automate it today. Not “eventually”
Usage
./cache.sh # purge + warm + QA
./cache.sh --warm-only # just warm (skip purge)
./cache.sh --purge-only # just purge (skip warm)
Or do nothing. deploy.sh calls it automatically after every deploy. The 30 minutes a day is gone. The sitemap is verified on every ship. And I’ll never purge the wrong site’s cache again.
Related:
- Flying Blind with Expensive Instruments — The same pattern: tooling that works but is pointed at the wrong thing
- AI Trusts Your Docs. That’s the Problem. — Why “looks right” isn’t “works right” — the deploy script said success for weeks
- /todo: The Execution Manager — Structured task tracking that pairs well with deploy automation