Varnish is brilliant when it works and confusing when it doesn’t. Most Varnish problems I see on commerce sites fall into four groups: nothing is cached, the wrong thing is cached, something cached never goes away, or the cache sits in front of the wrong traffic.
Start with the headers
Before reading any VCL, request the page twice and look at what comes back:
curl -sI https://www.example.com/some-category.html | grep -iE 'x-cache|age|cache-control|set-cookie|x-magento'
An Age header that never increases, a Set-Cookie on an anonymous page, or Cache-Control: no-store from the backend tells you where to look next. If there is a CDN in front of Varnish, check its headers separately so you know which layer you are debugging.
Problem 1: low hit rate
- Uncacheable blocks. In Magento, a single
cacheable="false"in a layout file makes every page using that layout uncacheable. - Cache-busting query strings. Marketing parameters such as
utm_*andgclidshould be stripped invcl_recvso they don’t create a new object per click. - Cookies on anonymous requests. A backend that sets a cookie on every response stops Varnish caching it.
- Short TTLs. Check what the application sends; a sensible TTL with reliable purging beats a tiny TTL.
varnishstat gives the overall hit ratio, and varnishlog -g request -q 'VCL_call eq "PASS"' shows exactly which requests are being passed and why.
Problem 2: personalised content leaking
This is the dangerous one: a customer’s name, cart or price tier appearing for someone else. The rule is that server-rendered HTML must be identical for everyone in the same cache context. Customer-specific data belongs in private content loaded by JavaScript (Magento’s customer sections) or in a cache key that genuinely varies, such as customer group for B2B pricing.
If you find a leak, purge the affected pages immediately and treat the fix as a priority release. Then add a test that checks a logged-in-only string never appears in cached HTML.
Problem 3: stale content that won’t purge
- Confirm the application is configured with the right Varnish host and port, and that purge requests are allowed from the application servers’ IPs in the VCL ACL.
- With several Varnish nodes, every node must receive the purge.
- Imports and integrations that write directly to the database bypass the events that trigger tag-based invalidation.
- A CDN in front of Varnish needs its own purge, or a short TTL for HTML.
Problem 4: caching the wrong traffic
Checkout, customer account, admin and API routes should always pass. Health-check endpoints should too, otherwise the load balancer may report a healthy cache in front of a broken backend.
A habit worth keeping
Keep the VCL in version control, deploy it like code and add a comment for every exception. Six months later, “why do we pass this URL?” should have an answer.