Varnish FPC vs. Built-in Cache in Magento 2: Decision Criteria for Practice
AI generated
M2
di.xml
Magento 2 · Caching · Performance · Infrastructure
Varnish FPC vs. Built-in Cache in Magento 2
Decision criteria for practice

Magento 2 already ships with a working caching solution in the form of its built-in full page cache, yet many projects put Varnish in front of the shop anyway without checking whether the actual requirements call for it. This article compares both approaches based on traffic volume, hosting setup and team skills, and uses VCL examples, purge strategies and monitoring commands to show when the extra operational effort of Varnish really pays off.

18 min read Varnish · VCL · Full Page Cache · Purge strategies Magento 2.4.x · Varnish 6/7 · Hyva compatible

1. Context: two cache layers, one misconception

Anyone setting up a new Magento 2 project almost inevitably runs into the question: do we need Varnish in front of the shop? In most cases this question is framed incorrectly, because it conflates two different things. Magento already ships with a built-in full page cache that stores entire pages as HTML and serves them on repeat requests without re-rendering the whole application. Varnish is not an alternative to this concept, it is a different implementation of the same idea: a proxy in front of everything that caches HTTP responses before a request ever reaches PHP.

The real difference is not "cache or don't cache", it is where in the request path caching happens. The built-in cache operates inside the PHP process and is controlled through Magento's cache frontends, while Varnish sits as a standalone reverse proxy in front of the entire web server stack and intercepts requests before Nginx or Apache are even involved. This architectural shift has consequences for performance, operational effort and infrastructure complexity that go well beyond the simple question of cache hit rate.

Instead of "do I need Varnish?", the question should be: which cache layer fits our traffic profile, our hosting setup, and the team's ability to maintain a VCL configuration long term? That decision framework is exactly what the following sections cover, with concrete configuration examples for both approaches.

2. Architecture of the built-in full page cache without Varnish

Without Varnish, Magento handles all HTTP caching itself. This is controlled via two configuration blocks in app/etc/env.php: the cache type full_page, which must be enabled in general, and the cache frontend, which determines where rendered pages are physically stored, typically Redis or the filesystem via Cm_Cache_Backend_File. If the http_cache_hosts key is left empty, Magento assumes no external reverse proxy exists and takes over delivering cached pages entirely itself.

Technically this runs through Magento\Framework\App\PageCache\Kernel: on every request, Magento first checks whether a cached response already exists for the current URL, the vary context and the cookie combination. If so, the stored HTML response is returned directly, but only after the PHP process has started and part of the bootstrap has run. That is the key difference from Varnish: the built-in cache saves the expensive rendering work, but not the full PHP bootstrap overhead per request.

For many shops with moderate traffic, that is exactly enough. The built-in cache needs no additional service, no open port, and no root access to the server, which makes it the only practical option on shared or managed hosting environments where running a dedicated reverse proxy is not possible.


<?php
// app/etc/env.php - built-in Full Page Cache without Varnish
return [
    'cache_types' => [
        'config' => 1,
        'layout' => 1,
        'block_html' => 1,
        'full_page' => 1,
        'translate' => 1,
    ],
    'cache' => [
        'frontend' => [
            'page_cache' => [
                'id_prefix' => 'a1b_',
                'backend' => 'Cm_Cache_Backend_File',
                'backend_options' => [
                    'cache_dir' => '/var/www/html/var/page_cache',
                ],
            ],
        ],
    ],
    // Empty array signals to Magento: no reverse proxy, use built-in cache
    'http_cache_hosts' => [],
];

3. Architecture with Varnish: VCL, debug header and purge flow

Once Varnish runs as a reverse proxy in front of Magento, the request path changes fundamentally. The http_cache_hosts entry in env.php is populated with the host and port of the Varnish server, and Magento generates a VCL file from the shipped template via bin/magento varnish:vcl:generate. This VCL defines which requests may be cached, how cookies are handled, and how purge requests are processed. Unlike the built-in cache, a cached request under active Varnish FPC never reaches the PHP process at all, it is answered entirely from Varnish's memory.

A key diagnostic tool is the X-Magento-Cache-Debug header, which Magento sets itself and which carries values like HIT or MISS. This header works regardless of whether Varnish is involved, but it shows its full effect only in combination with Varnish: on a HIT, the request never reached the PHP process at all. For invalidation, Magento sends HTTP PURGE requests with the header X-Magento-Tags-Pattern to Varnish whenever relevant entities such as products, categories or CMS blocks change. The VCL has to explicitly allow this PURGE method and restrict it to trusted sender IPs via an ACL.

The purge flow therefore runs entirely outside the normal page request: an editor saves a product, a Magento observer triggers tag invalidation, a PURGE request goes to Varnish, and Varnish removes exactly the affected cache entries based on the tag pattern, without flushing the rest of the cache. This granular mechanism is one of the main reasons why Varnish outperforms the built-in cache when content changes frequently.


sub vcl_recv {
    # Allow PURGE only from trusted Magento application servers
    if (req.method == "PURGE") {
        if (!client.ip ~ purge_acl) {
            return (synth(405, "Not allowed"));
        }
        return (purge);
    }

    # Never cache admin, checkout or customer account requests
    if (req.url ~ "^/(admin|checkout|customer)") {
        return (pass);
    }

    unset req.http.X-Forwarded-For;
    set req.http.X-Forwarded-For = client.ip;
    return (hash);
}

sub vcl_backend_response {
    # Surface Magento cache tags for tag-based purging
    if (beresp.http.X-Magento-Tags) {
        set beresp.http.X-Magento-Tags-Pattern = beresp.http.X-Magento-Tags;
    }

    set beresp.http.X-Magento-Cache-Debug = "MISS";
    set beresp.grace = 3600s;
    return (deliver);
}

4. Decision criteria for practice

Traffic volume is the most obvious criterion, but not the only one. A shop with a few thousand sessions per day barely notices the PHP bootstrap overhead of the built-in cache, while a shop with several hundred thousand requests per day feels every saved millisecond of rendering time directly in TTFB. Rule of thumb from practice: once load spikes reach the low hundreds of requests per second, the PHP bootstrap overhead of the built-in cache starts to matter compared to the practically bootstrap-free delivery of Varnish.

The hosting setup often decides in advance whether the question can even be asked. On shared or classic managed hosting without root access and without a dedicated reverse proxy port, Varnish generally cannot be run at all, leaving only the built-in cache. On dedicated hosting, VPS setups or Kubernetes clusters the technical barrier is much lower, so the decision depends more on TTFB targets and infrastructure complexity than on hard technical limits.

Team skill is the most commonly underestimated factor. A VCL configuration needs upkeep: every Magento upgrade should be checked for changes to the default VCL template, cookie handling has to be updated for new personalization features, and a faulty purge pattern can result in stale prices or stock levels being served for days. Teams without dedicated DevOps resources are often better off with the built-in cache, even if the infrastructure would technically allow Varnish.

5. VCL customizations specific to Magento

The default VCL generated by Magento covers the basics, but almost always needs customization for real shops. The most important area of customization is cookie handling for logged-in customers. Magento sets cookies like PHPSESSID and personalization markers at login that signal a response contains customer-specific content and must not be served from the shared cache. If this logic is missing from the VCL, in the worst case a customer's cart or greeting ends up in another visitor's cache, a serious privacy failure.

For personalized blocks such as the mini cart, customer greeting or recommendations, Magento relies on Edge Side Includes. The main page HTML body remains fully cacheable, while individual placeholders marked with esi:include tags are filled with current, uncached content only at delivery time by Varnish. This lets a shop with personalized elements still be served from cache at a high percentage, instead of re-rendering the entire page on every request.

This combination of cookie-based bypass and ESI-based partial delivery is the core of what makes Varnish so powerful in a Magento context, but it is also the part of the configuration that demands the most care during updates.


sub vcl_recv {
    # Bypass cache entirely for logged-in customers and active checkout
    if (req.http.Cookie ~ "X-Magento-Vary=") {
        return (pass);
    }

    # Strip tracking cookies that would otherwise fragment the cache
    if (req.http.Cookie) {
        set req.http.Cookie = regsuball(
            req.http.Cookie,
            "(^|; )(_ga|_gid|_fbp)=[^;]+",
            ""
        );
        if (req.http.Cookie == "") {
            unset req.http.Cookie;
        }
    }
    return (hash);
}

sub vcl_backend_response {
    # Enable ESI processing for personalized fragments (mini cart, greeting)
    if (bereq.url !~ "\.(css|js|png|jpg|svg|woff2?)$") {
        set beresp.do_esi = true;
    }
    return (deliver);
}

6. Cache invalidation and purge strategies compared

Both approaches use the same logical foundation: tag-based invalidation. Every cached response is tagged when it is generated, for example with the product ID, category ID or block name. When an entity changes, all cache entries with the matching tag are invalidated, without flushing the entire cache. The difference lies in execution: the built-in cache invalidates entries directly inside the PHP process via the configured cache frontends, while under active Varnish an additional HTTP PURGE request has to be sent to the reverse proxy so its store stays in sync as well.

From a developer's perspective this is largely transparent thanks to Magento's Service Contracts. Whether the built-in cache storage is cleaned in the background or a PURGE is additionally sent to Varnish is decided automatically by Magento based on the http_cache_hosts configuration. Custom code only needs to call the standard invalidation APIs and does not need to worry about which cache layer is actually active.

For custom modules or targeted cache cleanup after custom events, it is worth building a dedicated purge service that wraps the existing interfaces instead of invalidating tags scattered throughout the codebase.


<?php
declare(strict_types=1);

namespace Mironsoft\CacheTools\Service;

use Magento\Framework\App\Cache\TypeListInterface;
use Magento\PageCache\Model\Cache\Type as FullPageCache;

/**
 * Purges full page cache entries by tag.
 * Works identically whether the built-in cache backend or a
 * Varnish frontend is configured via http_cache_hosts.
 */
class CachePurger
{
    /**
     * @param TypeListInterface $cacheTypeList
     * @param FullPageCache $fullPageCache
     */
    public function __construct(
        private readonly TypeListInterface $cacheTypeList,
        private readonly FullPageCache $fullPageCache
    ) {
    }

    /**
     * Invalidates all cache entries matching the given tags.
     *
     * @param array $tags
     * @return void
     */
    public function purgeByTags(array $tags): void
    {
        $this->fullPageCache->clean(
            \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG,
            $tags
        );
        $this->cacheTypeList->invalidate(FullPageCache::TYPE_IDENTIFIER);
    }
}

7. Monitoring and debugging

Cache hit ratio is the key metric for judging whether a cache layer is doing its job, regardless of whether the built-in cache or Varnish is active. The simplest starting point is analyzing the X-Magento-Cache-Debug header via curl: a first request should return MISS, a second one right after should return HIT. If the second request still shows MISS, either there is a configuration error, or cookies are preventing the response from being cacheable at all.

With Varnish, varnishstat additionally provides aggregated hit ratio figures since the last service restart, and varnishlog lets you follow individual requests live, for example to check whether PURGE requests actually arrive and are processed with the expected tag pattern. The built-in cache offers no equivalent live tooling, here server logs and the debug header remain the main tools.

Important for reliable measurements: always test with the same request headers and without cookies, since both the built-in cache and Varnish create different cache entries for different cookie or header combinations depending on the vary configuration.


# Check cache debug header for a given URL (works for built-in cache and Varnish)
curl -sI https://shop.example.com/ | grep -i "X-Magento-Cache-Debug\|Age\|Cache-Control"

# Second request right after should show HIT if the cache is warm
curl -sI https://shop.example.com/ | grep -i "X-Magento-Cache-Debug"

# Varnish only: overall hit ratio since last service restart
varnishstat -1 | grep -E "cache_hit|cache_miss"

# Varnish only: live request log, filtered to PURGE calls
varnishlog -q "ReqMethod eq \"PURGE\""

# Compare TTFB with and without a warm cache
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\n" https://shop.example.com/

8. Migration between the built-in cache and Varnish

The path from the built-in cache to Varnish is technically straightforward: install Varnish, generate a VCL via bin/magento varnish:vcl:generate, reconfigure the web server so Varnish takes over the public port with Nginx or Apache listening behind it on an internal port, and finally set http_cache_hosts in env.php. The real effort is not in this basic installation, it is in adapting the VCL to project-specific personalization, cookie handling and ESI configuration that the default template does not cover.

Typical stumbling blocks during migration: SSL termination is often overlooked, because Varnish itself does not speak TLS and a terminator like Nginx or a load balancer is needed in front of it. A second common mistake is health checks or monitoring tools continuing to hit the application server directly instead of Varnish, producing skewed measurements. A third classic: after a Magento upgrade the default VCL template changes, but project-specific customizations are not updated, so new cache tags or changed purge logic simply have no effect.

The reverse path, from Varnish back to the built-in cache, is taken less often, but it happens, for example when switching to managed hosting without reverse proxy support. Here it is essential to fully clear http_cache_hosts in env.php and reconfigure the full_page cache type, otherwise Magento keeps trying to send PURGE requests to a Varnish host that no longer exists.

9. Varnish vs. built-in cache side by side

The table below summarizes the key decision criteria that in practice show whether the built-in cache is enough or whether Varnish FPC justifies the extra operational effort.

Criterion Built-in cache Varnish FPC Recommendation
TTFB / performance PHP bootstrap on every request, even on a cache hit Response without any PHP involvement Varnish once traffic is high
Hosting requirements Runs without root, suitable for shared hosting Needs a dedicated port and root access Decide by hosting type
Operational effort No separate service to maintain Own VCL upkeep required on every upgrade Check team skills beforehand
Invalidation Directly inside the PHP process Instant HTTP PURGE without PHP Varnish for high write load
Scaling Tied to PHP-FPM capacity Scales horizontally independent of the backend Varnish for growth forecasts

No single criterion decides on its own. A shop with moderate traffic on managed hosting rarely benefits noticeably from Varnish, while a growing shop with a dedicated server and clear TTFB targets usually recoups the extra operational effort quickly.

10. Summary

The choice between Varnish and the built-in full page cache in Magento 2 is not a matter of belief, it is a matter of traffic profile, hosting setup and available team skill. The built-in cache reliably does its job for small to mid-sized shops without additional infrastructure and without VCL upkeep, but it hits the limit of PHP bootstrap overhead at high traffic. Varnish FPC eliminates exactly that overhead, but in return demands dedicated hosting, clean cookie and ESI configuration, and continuous VCL maintenance with every Magento upgrade.

Anyone who makes the decision based on the criteria described in this article, instead of reaching for Varnish reflexively because it is supposedly best practice, avoids both unnecessary operational effort and real performance bottlenecks. Monitoring via the cache debug header and, with Varnish active, via varnishstat shows early on whether the chosen cache layer is actually delivering the expected effect.

Varnish vs. built-in cache in Magento 2: the essentials at a glance

Built-in cache

No additional service, runs on any hosting setup, ideal for small to mid-sized shops without root access.

Varnish FPC

Answers cached requests without any PHP involvement, requires dedicated hosting and ongoing VCL maintenance.

Purge strategy

Tag-based invalidation in both cases, with Varnish additionally via HTTP PURGE requests.

Monitoring

X-Magento-Cache-Debug header for both variants, varnishstat and varnishlog additionally with Varnish.

11. FAQ: Varnish vs. built-in cache in Magento 2

1What is the difference between the built-in cache and Varnish?
The built-in cache runs inside the PHP process and saves rendering work. Varnish sits in front and answers hits entirely without a PHP bootstrap.
2Do I need Varnish for a small shop?
Usually not. The built-in cache is enough at moderate traffic and works even without root access on shared hosting.
3What does X-Magento-Cache-Debug mean?
Shows HIT or MISS, works for both variants. With Varnish, HIT means the PHP process was never reached.
4How does the purge flow work?
Magento sends PURGE with X-Magento-Tags-Pattern to Varnish. Varnish removes exactly the matching cache entries, without flushing everything.
5Use the built-in cache and Varnish together?
Not usefully in parallel. Once http_cache_hosts is set, Varnish takes over the HTTP layer, full_page stays part of the configuration.
6What is ESI and what is it for?
Edge Side Includes deliver personalized blocks separately from the cacheable page body, so most of the page stays cacheable.
7How do I measure cache hit ratio?
With curl and the debug header for both variants. With Varnish, varnishstat provides additional aggregated figures.
8Pitfalls when migrating to Varnish?
SSL termination, health checks hitting the wrong endpoint, and outdated VCL after a Magento upgrade are the most common issues.
9Does Varnish work on shared hosting?
Generally not, since a dedicated port and usually root access are required. The built-in cache remains the practical option here.
10VCL not updated after an upgrade?
New cache tags or changed purge logic are not processed correctly, which can lead to stale content or unnecessary cache flushing.

Mironsoft

Magento 2 performance, caching architecture and hosting consulting

Varnish or the built-in cache, the right decision for your shop?

We analyze traffic profile, hosting setup and existing cache configuration and implement the right solution, from VCL tuning to a clean cache invalidation strategy.

Cache audit

Analysis of hit ratio, TTFB and existing VCL or cache configuration

Varnish setup

VCL customization for cookie handling, ESI and tag-based purge strategies

Monitoring

Cache hit ratio dashboards and alerting for purge failures