Your first visitor after a deploy shouldn’t be the one who pays for it.
That’s the entire idea behind a warmup cache request: an automated HTTP request sent to your important pages or API endpoints before real users arrive, so the expensive work of building a response — hitting the database, running application logic, rendering the page — already happened by the time someone actually loads the site.
This guide covers what a warmup request actually does, how to build one yourself with working code, the mistakes that make warmup scripts silently useless, and how to verify it’s actually working instead of just assuming it is.
What Happens Without Cache Warming
A cache is “cold” when nothing is stored for a given URL yet. The first request to a cold URL typically has to:
- Hit the CDN or reverse proxy edge.
- Miss, and get forwarded to the origin server.
- Run application logic (routing, templating, auth checks).
- Query the database or other backend services.
- Assemble the full response.
- Store that response in cache for next time.
That chain of work happens on someone’s real page load. If it’s slow, that person feels it — and if a lot of people hit the same cold page at once (say, right after a deploy), your origin can take a concurrency spike it wasn’t planned for.
A warmup request just does that first expensive round-trip on purpose, ahead of time, using a script instead of a customer.
How a Warmup Request Actually Works
Trigger (deploy / cron / CI pipeline)
│
▼
Warmup script sends GET request
│
▼
CDN / reverse proxy checks cache
│
┌────┴────┐
HIT MISS
│ │
done forwards to origin
│
▼
App builds response
│
▼
Response cached per
Cache-Control / Vary rules
The response headers your app returns — Cache-Control, ETag, Vary, Surrogate-Control (if you’re behind Fastly or similar) — are what actually determine whether that warmup request results in a cached entry at all. A warmup script can succeed at the HTTP level (200 OK) while doing nothing useful, if the response underneath isn’t cacheable. More on that below.
A Working Warmup Script
Here’s a minimal, rate-limited warmup script in Python — the kind you’d wire into a post-deploy CI step or a cron job. It reads URLs from a file, throttles concurrency so it doesn’t hammer your own origin, and logs cache status from response headers.
import asyncio
import aiohttp
import time
URLS_FILE = "warmup_urls.txt"
CONCURRENCY = 5 # don't blast your own origin
USER_AGENT = "WarmupBot/1.0 (+internal cache warmer)"
async def warm(session, url, sem):
async with sem:
start = time.perf_counter()
try:
async with session.get(url, headers={"User-Agent": USER_AGENT}) as resp:
elapsed = (time.perf_counter() - start) * 1000
cache_status = resp.headers.get("CF-Cache-Status") or \
resp.headers.get("X-Cache") or "unknown"
print(f"{resp.status} | {elapsed:.0f}ms | cache={cache_status} | {url}")
except Exception as e:
print(f"ERROR | {url} | {e}")
async def main():
with open(URLS_FILE) as f:
urls = [line.strip() for line in f if line.strip()]
sem = asyncio.Semaphore(CONCURRENCY)
async with aiohttp.ClientSession() as session:
await asyncio.gather(*(warm(session, u, sem) for u in urls))
if __name__ == "__main__":
asyncio.run(main())
warmup_urls.txt would just be your priority list, one URL per line:
https://example.com/
https://example.com/pricing
https://example.com/blog
https://example.com/api/products?featured=true
The key design choices here, and why they matter:
- A dedicated
User-Agent. Lets you filter warmup traffic out of your real analytics later, and lets your own logs (or a WAF) recognize it as legitimate automated traffic rather than a bot to block. - A concurrency semaphore. Warmup requests hit a cold origin. Firing 500 of them at once is a self-inflicted load spike — the exact thing you’re trying to prevent.
- Reading cache-status headers back.
CF-Cache-Status(Cloudflare),X-Cache(Varnish, Fastly, many CDNs), orAgetell you whether the request actually resulted in a cache write, not just a 200.
Platform-Specific Warmup Approaches
Generic scripts work everywhere, but most stacks have a built-in or idiomatic way to do this:
Cloudflare — Cache Reserve and tiered caching help, but for active warming you’re still typically sending real requests; Cloudflare also exposes CF-Cache-Status (HIT, MISS, EXPIRED, DYNAMIC) so you can verify results directly in your script’s response headers.
Varnish — you can pair varnishreload for VCL changes with a warmup step that curls your priority URL list immediately after; check X-Varnish and Age headers to confirm hits.
WordPress — plugins like WP Rocket, W3 Total Cache, and LiteSpeed Cache include sitemap-based cache preloaders that crawl your XML sitemap after a save or purge. These are effectively warmup scripts with a UI, and are usually the fastest path if you’re not going to write custom code.
Google App Engine (legacy standard environment) — has a first-class warmup hook: requests to /_ah/warmup run your warmup logic before the instance receives real traffic. If you’re on this platform, use it instead of an external script.
Next.js / Vercel / serverless in general — cache warming reduces requests reaching the function, but it doesn’t fully solve function cold starts. Pair it with provisioned concurrency (AWS Lambda) or minimum instances (Vercel, Cloud Run) if execution cold start is the actual bottleneck, not just cache misses.
When to Trigger a Warmup
The highest-value moments to run one:
- Immediately after a deployment
- Immediately after a cache purge or invalidation
- After a server or cache-layer restart
- Before a scheduled traffic event — product launch, marketing send, ad campaign going live
- After editing a high-traffic page (pricing, homepage, top landing pages)
The recommended sequence for CI/CD:
Deploy → Purge/Invalidate → Warm priority URLs → Verify cache hits → Alert on failures
You don’t need to warm your whole site. For most sites, 20–100 high-value URLs covers the vast majority of real traffic — homepage, top landing pages, top category/product pages, and any API endpoints your frontend calls on first load. Pull this list from analytics or server logs, not guesswork.
The Mistake That Silently Wastes the Whole Effort: Cache Key Mismatches
This is the part most warmup guides skip, and it’s the one that actually breaks people’s setups.
Your cache doesn’t necessarily store one version of a URL — it stores one version per cache key, and the key can include:
- Query parameters
- Cookies
Accept-Language/ locale- Device type (mobile vs desktop, via
Vary: User-Agentor a CDN-specific header) - Geographic region (multi-region CDNs)
If your Vary header includes Accept-Language and your warmup script only requests the English version, every non-English visitor still hits a cold cache — while your dashboard shows “warmup succeeded.” Before building a warmup process, check your actual Vary header and cache-key configuration, and make sure your warmup requests replicate the real variants your traffic uses, not just one of them.
Never Warm Personalized Content
Don’t run generic warmup requests against:
- Account dashboards
- Shopping carts
- Authenticated API responses
- Admin routes
- Any URL that returns different content per user
If a personalized response accidentally lands in a shared cache — one visitor’s dashboard served to another visitor — that’s not a performance bug, it’s a data exposure incident. Keep your warmup URL list restricted to genuinely public, identical-for-everyone content, and double check that those routes don’t include Set-Cookie or session-bound data before adding them to the list.
Verifying It Actually Worked
Sending 200 OK requests proves nothing on its own. Check:
| Signal | What it tells you |
|---|---|
| Cache hit ratio (post-warmup) | Are subsequent requests actually being served from cache? |
| TTFB, cold vs. warm | Direct before/after comparison on the same URL |
| Origin request count | If a “warmed” page keeps hitting origin, your cache rule or key is wrong |
| P95 / P99 TTFB | Averages hide the tail; percentiles show whether your slowest requests actually improved |
| Cache-status response header | HIT vs MISS vs EXPIRED, per-request, per-CDN |
Cache Warming Isn’t a Fix for a Slow Backend
If an uncached request takes four seconds because of a bad query or unoptimized template, warming makes the cached path fast — but every cache miss, expiry, and cache-bypassed request still takes four seconds. Warming buys you consistency on top of a backend that’s already reasonably fast. It doesn’t buy you a fast backend.
Cache Warming vs. Prefetching — Quick Distinction
- Cache warming: server-side, proactive, prepares known high-value content before general traffic arrives (your homepage, top products).
- Prefetching: often client-side, predictive, anticipates what one specific visitor might do next (preloading a product page they’re likely to click).
Both can run in the same system — warm your top pages globally after deploy, prefetch a visitor’s likely next click once they’re on the site.
Cache Warming Checklist
- [ ] Build your priority URL list from real analytics, not guesses
- [ ] Confirm
Cache-Control, TTL, andVaryrules on each URL before warming it - [ ] Exclude anything personalized, authenticated, or session-bound
- [ ] Trigger warmup automatically after deploy and after purge, via CI/CD
- [ ] Throttle concurrency — don’t spike your own origin
- [ ] Tag warmup traffic with a distinct User-Agent
- [ ] Warm every cache-key variant real users actually request (locale, device, region)
- [ ] Verify with cache-hit ratio and TTFB, not just HTTP status codes
- [ ] Check P95/P99 latency, not just averages
- [ ] Treat warming as a complement to backend performance work, not a substitute for it
FAQ
What is a warmup cache request? An automated HTTP request that populates a cache entry before a real visitor requests the same content, so the first real user doesn’t absorb the cost of building it.
How many URLs should I warm? Most sites see the bulk of the benefit from 20–100 high-traffic, high-value URLs rather than warming the entire site.
Does cache warming help SEO? Indirectly — a faster, more consistent TTFB gives the browser an earlier start on rendering, which can support Largest Contentful Paint. It’s not a ranking factor on its own and won’t offset genuinely poor page performance.
Can warmup requests overload my server? Yes, if unthrottled. A burst of simultaneous cold requests is effectively a self-inflicted traffic spike — always rate-limit or batch them.
Is cache warming safe for logged-in or personalized pages? No, not in a shared cache. Restrict warmup lists to public, identical-for-everyone content only.
How do I confirm a warmup actually worked? Check the cache-status response header (CF-Cache-Status, X-Cache, etc.), compare TTFB cold vs. warm on the same URL, and watch whether origin request volume actually drops for that page.
