You've spent weeks tuning your Magento 2 Full Page Cache. Varnish is configured, FPC hit rates are above 90%, and your TTFB is under 200ms. Then you deploy a hotfix at 2 AM and flush the cache. Suddenly, every customer who visits gets a cold, uncached response — PHP bootstrapping, layout building, block rendering — the full stack, for every single request.
This is the cold cache problem, and it's one of the most overlooked performance gaps in Magento 2 operations. Cache warming is the solution: proactively generating cached responses before real users arrive.
Why Cache Warming Matters
Magento 2's Full Page Cache is lazy by default. A page only gets cached after the first request hits the backend. On large stores with thousands of product, category, and CMS pages, this means:
- Every deploy causes a performance cliff until traffic naturally re-warms the cache
- Cache flushes during low-traffic windows (maintenance, reindexing) leave mornings slow
- Bots and crawlers that hit un-cached pages waste server resources on expensive renders
A good warming strategy fills the cache before users notice, turning the cold-start problem into a non-event.
Strategy 1: Sitemap-Based Crawling
The simplest and most maintainable approach. Magento generates a sitemap at pub/sitemap.xml (or a sitemap index). Use this as your source of truth.
#!/bin/bash
# warm-cache.sh — crawl all URLs from sitemap
SITEMAP_URL="https://your-store.com/sitemap.xml"
CONCURRENCY=4
USER_AGENT="MagevantaCacheWarmer/1.0"
# Extract URLs from sitemap and crawl with wget
wget -q -O - "$SITEMAP_URL" \
| grep -oP '(?<=<loc>)[^<]+' \
| xargs -P "$CONCURRENCY" -I{} \
curl -s -o /dev/null -A "$USER_AGENT" \
-H "X-Forwarded-Proto: https" \
--max-time 10 "{}"
echo "Cache warm complete."
Set CONCURRENCY based on your server capacity. For most stores, 4–8 parallel requests is safe without overwhelming the backend. Run this script immediately after every deploy.
Pro tip: If you have a sitemap index (<sitemapindex>), parse the child sitemaps first:
wget -q -O - "$SITEMAP_URL" \
| grep -oP '(?<=<loc>)[^<]+\.xml' \
| xargs -I{} wget -q -O - {} \
| grep -oP '(?<=<loc>)[^<]+' \
| xargs -P 4 -I{} curl -s -o /dev/null "{}"
Strategy 2: Magerun2 Integration
If you use n98-magerun2 in your workflow, the sys:url:list command gives you all store URLs programmatically — respecting store views, locales, and URL configurations.
# Generate all URLs from Magerun and warm them
php /usr/local/bin/n98-magerun2 sys:url:list \
--add-all-stores \
--format=plain \
| grep '^http' \
| xargs -P 6 -I{} curl -s -o /dev/null \
-H "X-Forwarded-Proto: https" \
-A "CacheWarmer/1.0" "{}"
This is particularly useful for multistore setups where you have multiple base URLs that may not all appear in a single sitemap.
Strategy 3: Prioritized Warming
Not all pages are equal. Your homepage, top category pages, and bestseller PDPs get 10x more traffic than the long tail. Warm high-priority pages first.
Create a warm-priority.txt with your most important URLs:
https://your-store.com/
https://your-store.com/sale.html
https://your-store.com/new-arrivals.html
https://your-store.com/men.html
https://your-store.com/women.html
https://your-store.com/brands.html
Then in your deploy pipeline:
# Phase 1: warm critical pages immediately (sequential for reliability)
while IFS= read -r url; do
curl -s -o /dev/null "$url"
done < warm-priority.txt
echo "Priority pages warmed. Starting full crawl in background..."
# Phase 2: warm the rest asynchronously
nohup ./warm-cache.sh &>/var/log/cache-warm.log &
This ensures the most important pages are cached within seconds of deploy, while the full warm runs in the background.
Strategy 4: Varnish PURGE + Warm Pipeline
If you're running Varnish in front of Magento, you can build a smarter pipeline: instead of warming everything after a flush, only warm pages that were actually purged.
Magento sends PURGE requests to Varnish when cache tags are invalidated (e.g., after saving a product). You can intercept these in Varnish using a custom VCL logging hook, then re-crawl only those URLs.
sub vcl_recv {
if (req.method == "PURGE") {
# Log purged URLs for the re-warmer
std.log("PURGE:" + req.url);
}
}
Parse the Varnish log with varnishlog and feed purged URLs to your crawler. This is advanced but dramatically reduces warming time on large stores.
Strategy 5: Magento 2 Cron-Based Warming
For stores that flush cache during maintenance windows (e.g., after a reindex), add a Magento cron job that triggers warming automatically.
Create a simple cron class in your module:
namespace Vendor\CacheWarmer\Cron;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Sitemap\Model\ResourceModel\Sitemap\CollectionFactory;
class WarmCache
{
public function __construct(
private Curl $curl,
private CollectionFactory $sitemapCollectionFactory
) {}
public function execute(): void
{
$sitemaps = $this->sitemapCollectionFactory->create();
foreach ($sitemaps as $sitemap) {
$this->warmFromSitemap($sitemap->getSitemapUrl());
}
}
private function warmFromSitemap(string $url): void
{
$this->curl->get($url);
$body = $this->curl->getBody();
preg_match_all('/<loc>([^<]+)<\/loc>/', $body, $matches);
foreach ($matches[1] as $pageUrl) {
$this->curl->get($pageUrl);
}
}
}
Register this in crontab.xml to run after peak cache-clearing operations.
Warming with Customer Context
One subtlety: Magento FPC can vary by customer group (not logged in, logged in, specific groups). By default, only the CUSTOMER_GROUP_ID=0 (guest) variant is cached via FPC. Your warmer should simulate guest requests:
curl -s -o /dev/null \
-b "" \
-H "X-Forwarded-Proto: https" \
-H "Accept-Encoding: gzip" \
"$URL"
Avoid sending session cookies in warming requests — this will generate separate cache variations per session and negate the warming benefit.
Deploy Pipeline Integration
The ideal warming workflow integrates directly into your CI/CD pipeline. Using GitHub Actions:
- name: Warm Magento Cache
run: |
echo "Waiting for Varnish to propagate purges..."
sleep 10
chmod +x ./scripts/warm-cache.sh
./scripts/warm-cache.sh
env:
STORE_URL: ${{ secrets.STORE_URL }}
Add this step after php bin/magento cache:flush and before removing the maintenance flag. Users won't be let in until the critical pages are already warm.
Measuring Warming Effectiveness
Track your warming success with:
# Check FPC hit rate in Magento logs
grep "cache_type.*full_page" var/log/system.log | tail -50
# Check Varnish hit rate
varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss \
| awk '{print $1, $2}' \
| paste - - \
| awk '{printf "Hit rate: %.1f%%\n", $2/($2+$4)*100}'
A well-warmed cache should hit 80–90% within 5 minutes of a deploy on a typical store.
Key Takeaways
- Always warm after deploy — don't let users hit the cold cache
- Prioritize high-traffic pages — warm them first, synchronously
- Use your sitemap — it's the simplest accurate source of pages to warm
- Set concurrency carefully — too many parallel requests can spike your backend
- Integrate into CI/CD — make warming automatic, not manual
Cache warming is a small operational investment with a disproportionate impact on perceived performance. Your store should feel equally fast at 2:01 AM post-deploy as it does at peak traffic — and with a solid warming strategy, it will.
Top comments (0)