Varnish

Varnish answers cached pages without running PHP; Magento generates its VCL and purges it by tag pattern.

Syntaxbin/magento varnish:vcl:generate --export-version=7 --output-file=/etc/varnish/default.vcl

The built-in page cache stores rendered HTML, but PHP still boots, loads the framework and looks the entry up. Varnish sits in front of the application and answers a cached page from memory without touching PHP at all. That is why production runs Varnish and local development usually does not.

Switching it on

The caching application is a config value: system/full_page_cache/caching_application, where 1 is the built-in cache and 2 is Varnish. Set it with bin/magento config:set or in Stores > Configuration > Advanced > System > Full Page Cache, then run bin/magento cache:flush.

The VCL

Magento generates Varnish's configuration rather than expecting you to write it. bin/magento varnish:vcl:generate takes --access-list, --backend-host, --backend-port, --grace-period, --export-version and --output-file. Supported versions are 6 and 7; the default is 6. Without --output-file the VCL goes to standard output, so --output-file=/etc/varnish/default.vcl and a > redirect to the same path are equivalent. Reload Varnish afterwards: no bin/magento command touches the file on disk.

Purging and ESI

Invalidation is a PURGE request carrying X-Magento-Tags-Pattern, sent by Magento_CacheInvalidate to the hosts in the access list. A block with a ttl attribute in layout XML is replaced by an <esi:include> tag, which Varnish fetches separately with its own lifetime. Adobe Commerce on Cloud uses Fastly instead of Varnish, with its own module.

Example

Example · php
<?php
// app/code/Vendor/Ops/Console/Command/PurgeTagsCommand.php
declare(strict_types=1);

namespace Vendor\Ops\Console\Command;

use Magento\CacheInvalidate\Model\PurgeCache;
use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class PurgeTagsCommand extends Command
{
    public function __construct(
        private readonly PurgeCache $purgeCache,
        ?string $name = null
    ) {
        parent::__construct($name);
    }

    protected function configure(): void
    {
        $this->setName('vendor:varnish:purge');
        $this->setDescription('Sends a PURGE request to the configured Varnish hosts');
        $this->addArgument(
            'tags',
            InputArgument::IS_ARRAY | InputArgument::REQUIRED,
            'Cache tags, for example cat_p_42'
        );
        parent::configure();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $tags = $input->getArgument('tags');
        $purged = $this->purgeCache->sendPurgeRequest($tags);
        $output->writeln($purged ? 'Purged: ' . implode('|', $tags) : 'Purge failed');

        return $purged ? Cli::RETURN_SUCCESS : Cli::RETURN_FAILURE;
    }
}

// $ bin/magento vendor:varnish:purge cat_p_42 cat_c_3
// Purged: cat_p_42|cat_c_3

When to use it

  • Moving a store from the built-in page cache to Varnish before launch.
  • Regenerating the VCL after the backend host or access list changes in an infrastructure move.
  • Caching a promotional banner separately from the page it sits on, using ESI.

More examples

Generating and loading the VCL

The order matters. config:set writes the values the generator reads, so generating the VCL before setting the backend host bakes in the old one. Nothing in Magento reloads Varnish, so the last step is always a service reload. Rerun the whole sequence whenever the backend host, port, grace period or access list changes.

Example · bash
$ bin/magento config:set system/full_page_cache/caching_application 2
$ bin/magento config:set system/full_page_cache/ttl 86400
$ bin/magento cache:flush

$ bin/magento varnish:vcl:generate \
    --access-list=localhost,10.0.0.0/8 \
    --backend-host=127.0.0.1 \
    --backend-port=8080 \
    --grace-period=300 \
    --export-version=7 \
    --output-file=/etc/varnish/default.vcl

$ sudo systemctl reload varnish

# Equivalent to the --output-file form, same bytes:
$ bin/magento varnish:vcl:generate --export-version=7 > /etc/varnish/default.vcl

An ESI block

A positive ttl makes Magento emit <esi:include src="..."/> in place of the block's HTML, so Varnish caches the banner for 300 seconds independently of the page around it. The surrounding page stays cacheable, which is the point: ttl is the alternative to cacheable="false" for a fragment that changes on its own schedule. ESI blocks are excluded from the page's X-Magento-Tags header, so they expire by time rather than by purge. Run bin/magento cache:clean layout full_page after adding it.

Example · xml
<!-- app/code/Vendor/Promo/view/frontend/layout/default.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="header.panel">
            <block class="Magento\Framework\View\Element\Template"
                   name="vendor.promo.banner"
                   template="Vendor_Promo::banner.phtml"
                   ttl="300"/>
        </referenceContainer>
    </body>
</page>

Confirming Varnish is answering

A header check is the fastest diagnosis. X-Magento-Tags is set by Magento from every block implementing IdentityInterface and is what a purge pattern matches against; if it is missing, the page was generated as uncacheable. An Age that never rises points at the cache being bypassed, usually a cacheable="false" block or a cookie Varnish refuses to cache.

Example · bash
$ curl -sI https://example.com/women.html | grep -i 'x-magento\|age\|cache-control'
Cache-Control: max-age=86400, public, s-maxage=86400
Age: 412
X-Magento-Tags: cat_c_20,cat_p_11,cat_p_12

# Age greater than 0 means the copy came from Varnish.
# Age always 0 means every request is reaching PHP.

Discussion

  • Be the first to comment on this lesson.