DEV Community

Magevanta
Magevanta

Posted on • Originally published at magevanta.com

Magento 2 Varnish Configuration: The Production-Ready Setup

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
Enter fullscreen mode Exit fullscreen mode

Configure Varnish to listen on port 80, proxy to your web server on port 8080:

/etc/varnish/default.vcl
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Generate the Magento-recommended VCL:

bin/magento varnish:vcl:generate --export-version=7 > /etc/varnish/default.vcl
service varnish restart
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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"));
    }
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Target hit rate: > 85% for anonymous traffic. If you're below this, check:

  1. Are you stripping UTM parameters? (cache fragmentation)
  2. Are logged-in user requests being passed correctly?
  3. 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)