Varnish Cache Setup for Magento 2

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Setting Up Varnish for Magento 2 Caching

Varnish is HTTP accelerator working before web server. For Magento 2 it's standard production stack: without it site with 500+ concurrent visitors saturates CPU on PHP-FPM. Magento 2 has built-in Varnish support via ESI and X-Magento-Tags for cache invalidation.

Stack Architecture

Browser → Varnish :80/:443 → Nginx :8080 → PHP-FPM → MySQL/Redis
                         ↘ ESI requests (cart blocks, auth)

Varnish listens on 80/443, nginx moved to port 8080 receiving only Varnish traffic. For SSL termination, nginx or HAProxy on 443 proxies to Varnish 80.

Varnish Installation

# Ubuntu 22.04
curl -s https://packagecloud.io/install/repositories/varnishcache/varnish74/script.deb.sh | sudo bash
apt install varnish

# Service file
systemctl edit varnish
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd \
  -a :80 \
  -T localhost:6082 \
  -f /etc/varnish/default.vcl \
  -s malloc,2g \
  -p thread_pools=2 \
  -p thread_pool_max=1000 \
  -p thread_pool_timeout=300

Size -s malloc,2g—for Magento cache 2–4 GB sufficient. Less than 1 GB pointless: Magento pages weigh 80–200 KB and small cache means low hit rate.

VCL Configuration for Magento 2

Magento 2 provides ready VCL via admin: Stores > Configuration > Advanced > System > Full Page Cache > Varnish Configuration > Export VCL. Export and use as base, but standard VCL needs fixes.

Key VCL sections:

vcl 4.1;

import std;

backend default {
    .host = "127.0.0.1";
    .port = "8080";
    .connect_timeout = 600s;
    .first_byte_timeout = 600s;
    .between_bytes_timeout = 600s;
}

acl purge {
    "localhost";
    "127.0.0.1";
}

sub vcl_recv {
    # Pass real IP
    if (req.restarts == 0) {
        if (req.http.X-Forwarded-For) {
            set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
        } else {
            set req.http.X-Forwarded-For = client.ip;
        }
    }

    # PURGE requests from Magento
    if (req.method == "PURGE") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed"));
        }
        return (purge);
    }

    # BAN by X-Magento-Tags (block invalidation)
    if (req.method == "BAN") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed"));
        }
        if (req.http.X-Magento-Tags-Pattern) {
            ban("obj.http.X-Magento-Tags ~ " + req.http.X-Magento-Tags-Pattern);
        }
        return (synth(200, "Banned"));
    }

    # Don't cache HTTPS indicator
    if (req.http.X-Forwarded-Proto == "https") {
        set req.http.Ssl-Offloaded = "1";
    }

    # Don't cache cart, checkout, account
    if (req.url ~ "/(checkout|customer|account|cart|wishlist)") {
        return (pass);
    }

    # Remove cookies on static resources
    if (req.url ~ "\.(css|js|png|jpg|jpeg|webp|gif|ico|woff2|svg)(\?.*)?$") {
        unset req.http.Cookie;
        return (hash);
    }

    # Normalize Google Analytics parameters
    set req.url = regsuball(req.url, "(^|&)(utm_[a-z]+|gclid|gclsrc|fbclid)=[^&]*", "");
    set req.url = regsub(req.url, "^(.*)\?&?(.*)?$", "\1?\2");
    set req.url = regsub(req.url, "^(.*)\?$", "\1");

    return (hash);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }

    # Different cache for HTTP/HTTPS
    if (req.http.Ssl-Offloaded) {
        hash_data(req.http.Ssl-Offloaded);
    }

    return (lookup);
}

sub vcl_backend_response {
    # Don't cache 5xx
    if (beresp.status >= 500) {
        set beresp.uncacheable = true;
        set beresp.ttl = 1s;
        return (deliver);
    }

    # TTL by content type
    if (beresp.http.content-type ~ "text/html") {
        set beresp.ttl = 1d;
        set beresp.grace = 1h;
    }

    if (bereq.url ~ "\.(css|js|woff2)(\?.*)?$") {
        set beresp.ttl = 1y;
    }

    # Remove cookies from cached responses
    if (beresp.ttl > 0s) {
        unset beresp.http.Set-Cookie;
    }

    return (deliver);
}

sub vcl_deliver {
    # Debug headers (disable on prod)
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
        set resp.http.X-Cache-Hits = obj.hits;
    } else {
        set resp.http.X-Cache = "MISS";
    }

    # Remove Magento internal headers
    unset resp.http.X-Magento-Tags;
    unset resp.http.X-Powered-By;
    unset resp.http.Server;

    return (deliver);
}

Magento 2 Configuration

Admin > Stores > Configuration > Advanced > System > Full Page Cache:

  • Caching Application: Varnish Cache
  • TTL for public content: 86400 (24 hours)

Admin > Stores > Configuration > Advanced > System > Full Page Cache > Varnish Configuration:

  • Access list: 127.0.0.1
  • Backend host: 127.0.0.1
  • Backend port: 8080

Enable FPC:

bin/magento cache:enable full_page
bin/magento config:set system/full_page_cache/caching_application 2
# 2 = Varnish, 1 = Magento built-in

ESI — Dynamic Blocks Inside Cached Pages

Magento 2 uses ESI (Edge Side Includes) for personalized blocks: cart, username, wishlist. ESI already enabled in VCL via beresp.do_esi = true, which Magento sets in X-Esi: 1 header.

Check ESI works:

curl -I https://myshop.ru/ | grep X-Cache
# Should return: X-Cache: HIT

curl -I https://myshop.ru/checkout/cart/ | grep X-Cache
# Should return: X-Cache: MISS (cart not cached)

Cache Invalidation

Magento auto-invalidates cache on product/category changes via BAN by tags:

# Manual invalidate specific page
curl -X PURGE http://127.0.0.1:80/catalog/product/view/id/42

# Invalidate all cache
varnishadm "ban req.url ~ /"

# Cache statistics
varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss

Target hit rate for Magento—85–95%. Below 70% indicates problem: either too many uncacheable requests or VCL handles cookies incorrectly.

Monitoring

# Real-time requests and status
varnishlog -q 'RespHeader:X-Cache' -i RespHeader,ReqURL,RespStatus

# Top uncacheable URLs
varnishlog -q 'RespHeader:X-Cache eq MISS' -i ReqURL | \
  awk '{print $2}' | sort | uniq -c | sort -rn | head -20

Nginx as SSL Terminator

server {
    listen 443 ssl http2;
    server_name myshop.ru;

    ssl_certificate /etc/letsencrypt/live/myshop.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myshop.ru/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Ssl-Offloaded "1";
    }
}

Timeline

Varnish installation, VCL config for Magento 2, nginx move to 8080, SSL termination setup: 1–2 days. Hit rate testing, invalidation, ESI blocks: 1 day. Load testing and parameter tuning (malloc, thread_pools): 0.5–1 day.