Varnish is the gold standard for full-page caching in front of Magento. Configured correctly, it serves the vast majority of your traffic without touching PHP at all — response times under 5ms. Configured incorrectly, it creates subtle bugs and a false sense of security.
Here's the production-ready setup.
Why Varnish over Magento's built-in FPC
Magento ships with a built-in full page cache (backed by filesystem or Redis). Varnish is better for high-traffic stores because:
- It sits in front of PHP entirely — Varnish serves cached responses without starting PHP-FPM at all
- Massive throughput — Varnish can serve tens of thousands of requests per second on modest hardware
- ESI support — Edge Side Includes let you cache most of a page while keeping dynamic fragments fresh
- Sophisticated TTL control — per-URL, per-content-type, per-header rules
For stores under ~10k daily visits, Redis FPC is often sufficient. Above that, Varnish pays for itself.
Installing and wiring up Varnish
Install Varnish 7.x on Ubuntu/Debian:
apt-get install varnish
Configure Varnish to listen on port 80, proxy to your web server on port 8080:
/etc/varnish/default.vcl
Tell Magento to use Varnish:
bin/magento config:set system/full_page_cache/caching_application 2
bin/magento config:set system/full_page_cache/varnish/access_list "127.0.0.1"
bin/magento config:set system/full_page_cache/varnish/backend_host "127.0.0.1"
bin/magento config:set system/full_page_cache/varnish/backend_port "8080"
Generate the Magento-recommended VCL:
bin/magento varnish:vcl:generate --export-version=7 > /etc/varnish/default.vcl
service varnish restart
The critical VCL settings most guides miss
1. Normalize Accept-Encoding headers
Without this, Varnish caches separate entries for gzip, deflate, and uncompressed responses — tripling your cache size:
sub vcl_recv {
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|ico|mp4|ogg|swf|flv)$") {
unset req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} else {
unset req.http.Accept-Encoding;
}
}
}
2. Strip marketing query parameters
?utm_source=google&utm_campaign=spring creates separate cache entries for every ad campaign click. Strip them:
sub vcl_recv {
set req.url = regsuball(req.url, "[?&](utm_source|utm_medium|utm_campaign|utm_content|gclid|fbclid)=[^&]+", "");
set req.url = regsub(req.url, "^([^?]*)\?$", "\1");
}
3. Never cache logged-in sessions
Magento sets a X-Magento-Vary cookie for logged-in users. Ensure Varnish passes these through:
sub vcl_recv {
if (req.http.cookie ~ "PHPSESSID" || req.http.cookie ~ "frontend") {
return (pass);
}
}
ESI for dynamic content
ESI (Edge Side Includes) lets you cache 95% of a page in Varnish while keeping specific fragments dynamic — like the cart count, customer name, or "recently viewed" widget.
In your layout XML:
<block class="Magento\PageCache\Block\Esi" name="cart.count">
<arguments>
<argument name="handles" xsi:type="array">
<item name="default" xsi:type="string">default</item>
</argument>
<argument name="ttl" xsi:type="string">600</argument>
</arguments>
</block>
Varnish assembles the final page by fetching the ESI fragment separately (served from a shorter-TTL cache or PHP directly).
Enable ESI in Varnish:
sub vcl_backend_response {
set beresp.do_esi = true;
}
Cache purging on content updates
When a product is updated, Magento sends a BAN request to Varnish to invalidate affected cache entries. Your VCL must handle this:
acl purge {
"127.0.0.1";
}
sub vcl_recv {
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return (synth(403, "Not allowed"));
}
return (purge);
}
if (req.method == "BAN") {
if (!client.ip ~ purge) {
return (synth(403, "Not allowed"));
}
ban("obj.http.X-Magento-Tags ~ " + req.http.X-Magento-Tags-Pattern);
return (synth(200, "Banned"));
}
}
This enables Magento's tag-based Varnish purging — only pages containing the updated product/category are invalidated, not your entire cache.
Monitoring Varnish
Key metrics to watch:
# Overall hit rate
varnishstat -1 -f "MAIN.cache_hit" -f "MAIN.cache_miss"
# Current request rate
varnishstat -1 -f "MAIN.client_req"
# Live request log
varnishlog -i ReqURL -q "ReqMethod eq 'GET'"
# Objects in cache
varnishstat -1 -f "MAIN.n_object"
Target hit rate: > 85% for anonymous traffic. If you're below this, check:
- Are you stripping UTM parameters? (cache fragmentation)
- Are logged-in user requests being passed correctly?
- Are your TTLs too short?
Common mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| Not stripping UTM params | Low hit rate | Add regex filter in vcl_recv |
| Caching 404 pages | Cache pollution | Set short TTL for non-2xx responses |
| Not normalizing Accept-Encoding | 3× cache size | Strip and re-set header |
| Missing BAN ACL | Cache never purges | Add purge ACL and handler |
| ESI not enabled | Dynamic content cached | Set beresp.do_esi = true
|
Varnish + tag-based purging + ESI for dynamic fragments is the fastest Magento caching architecture available. Get the configuration right and you'll serve millions of requests without breaking a sweat.
Originally published on magevanta.com
Top comments (0)