Designing Magento 2 Full-Page Cache with Varnish and Redis Sessions
An architecture note for Magento 2's Full-Page Cache: the minimal Varnish + Redis session design, trust boundaries that prevent leaking private content, operational checks, failure modes, and when to redesign.
16 Feb 2026, 13:26 UTC

Magento 2 is slow by default: every uncached page render touches the database, the layout engine, and dozens of PHP modules. The supported fix is the built-in Full-Page Cache (FPC) backed by Varnish, with sessions moved out of the filesystem and into Redis. Done well, anonymous page loads drop from seconds to tens of milliseconds. Done carelessly, you serve one customer's cart to another or lose every active session when Redis restarts. This note covers the smallest architecture that works, where the trust boundaries sit, and what to watch in production.
Assumptions: Magento Open Source or Adobe Commerce 2.4.x, Varnish 6.x or 7.x, Redis 6+, and a single web node. Verify version-specific configuration keys against your release before applying.
Requirements
The design has to satisfy four things:
- Anonymous catalog and CMS pages are served from cache without hitting PHP.
- Logged-in customers, carts, and checkout always bypass or vary the cache correctly.
- Sessions survive PHP-FPM restarts and are shared if you later add a second web node.
- Content changes (product edits, price updates, CMS blocks) invalidate cached pages promptly.
The smallest suitable design
Three components, one request path:
- Varnish listens on port 80/443 (typically behind TLS termination) and serves cached responses. Magento's admin can export a matching VCL (Varnish Configuration Language) file; use it rather than hand-writing one, because the hash rules must match what Magento expects.
- Magento runs behind Varnish on a private port (e.g., 8080) with FPC set to "Varnish Cache" in
Stores > Configuration > Advanced > System. - Redis holds sessions in one logical database and, optionally, the default cache in another. Keep sessions and cache in separate Redis databases (or separate instances) so a cache flush never wipes sessions.
Session configuration lives in app/etc/env.php:
'session' => [
'save' => 'redis',
'redis' => [
'host' => '127.0.0.1',
'port' => '6379',
'database' => '2',
'password' => 'change-me',
'max_concurrency' => '6',
'disable_locking' => '0',
],
],Apply with bin/magento setup:config:set --session-save=redis ... or by editing env.php directly, then bin/magento cache:flush. This changes state, so test on staging first; rolling back means reverting env.php to 'save' => 'files' and flushing again.
Trust and data boundaries
The critical boundary is between anonymous and authenticated traffic. Varnish decides this from cookies, not from Magento. The stock VCL strips cookies for cacheable requests and passes PHPSESSID-bearing requests to the backend. Two rules keep this safe:
- Never cache a response that sets a session cookie or contains private content. Magento marks personalized blocks with
cacheable="false"or via private content (sections loaded by AJAX after the page). Do not remove those markers to "improve hit rate." - Keep query parameters out of the cache key where they don't affect content (tracking parameters like
utm_*), but never collapse parameters that do affect output. Over-aggressive stripping causes wrong-content bugs; under-stripping fragments the cache.
The second boundary is the purge channel. Magento invalidates Varnish by sending HTTP PURGE requests with cache tags (e.g., cat_p_123 for a product). Restrict PURGE to the Magento host's IP in the VCL ACL — an open PURGE endpoint lets anyone flush your cache. Redis should bind to localhost or a private interface with requirepass set; sessions contain customer identifiers and must not be reachable from the network edge.
Operational checks
After deployment, verify each layer independently:
- FPC enabled: run
bin/magento cache:statuson the Magento host and confirmfull_pageis enabled. - Varnish serving: request a catalog page twice with
curl -sI https://store.example.com/category.html. Magento sets debug headers when developer/debug mode allows it; look forX-Magento-Cache-Debug: HITon the second request. In production mode these headers may be suppressed — checkvarnishlogorvarnishstatinstead. - Sessions in Redis: run
redis-cli -n 2 KEYS 'sess:*' | head(orSCANin production —KEYSblocks on large datasets) and confirm keys exist with TTLs matching your session lifetime (redis-cli -n 2 TTL sess:<id>). - Invalidation works: edit a product in admin, then check
varnishlogfor an incoming PURGE and confirm the storefront reflects the change without a manual flush.
Ongoing, track Varnish hit ratio (healthy anonymous-heavy stores often exceed 90%, but establish your own baseline), Redis memory versus its maxmemory, and purge latency. Alert on Redis memory above roughly 80% — when it evicts, it evicts sessions.
Failure modes
- Redis down or evicting: customers are logged out and carts appear lost. Mitigation: dedicated Redis instance for sessions with
maxmemory-policy noeviction(or a policy that never touches session keys), plus persistence (AOF) if brief session loss is unacceptable. Accept that in-memory sessions are not durable by default. - Varnish down: traffic bypasses to Magento directly and load spikes. Put health checks in front and consider a fallback that fails closed rather than hammering PHP.
- VCL/Magento mismatch: after a Magento upgrade, regenerate and redeploy the VCL. Mismatches cause cache corruption symptoms — stale pages, or worse, cached private content. This is the highest-severity failure: an anonymous user seeing account data. Test account and checkout flows explicitly after every VCL change.
- Purge queue backlog: heavy catalog imports generate floods of PURGEs. If invalidation lags, editors see stale content and start flushing everything, which collapses the hit rate.
When this design stops fitting
Revisit the architecture when: you add a second web node (Redis session sharing becomes mandatory, and Varnish needs a load-balancing tier or per-node Varnish); you go multi-region (a single Redis/Varnish pair won't span regions — you need per-region stacks or a CDN); or you move to a headless/PWA frontend, where most caching shifts to the CDN and GraphQL layer and Varnish's role shrinks considerably. Until then, three components and a disciplined VCL are enough.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.