Skip to content

Varnish unable to Cache Magento 2.4.3

Continuing my issue from this thread.

I think it’s not caching due to Cache-Control header. Firstly, let me share my updated VCL configuration below maybe my VCL has an issue somewhere.

vcl 4.0;

import std;
include "./vars.vcl";
include "./pagespeed-requirement.vcl";

sub vcl_recv {
if (req.restarts > 0) {
    set req.hash_always_miss = true;
}

if (req.http.host ~ "www.domain1.com" || req.http.host ~ "domain1.com") {
    set req.backend_hint = domain1;
    return (pass);
} else if (req.http.host ~ "www.domain2.com" || req.http.host ~ "domain2.com") {
    set req.backend_hint = domain2;
} else if (req.http.host ~ "career.domain3.com") {
    set req.backend_hint = career;
    return (pass);
} else {
    set req.backend_hint = default;
    return (pass);
}

if (req.url ~ "^/.well-known/") {
  return (pass);
}

# Remove the proxy header to mitigate the httpoxy vulnerability
# See https://httpoxy.org/    
unset req.http.proxy;


if (req.method == "PURGE") {
    if (client.ip !~ purge) {
        return (synth(405, "Method not allowed"));
    }
    
# capistrano-magento2 gem for purging old content from varnish during it's deploy routine.
    if (!req.http.X-Magento-Tags-Pattern && !req.http.X-Pool) {
        return (synth(400, "X-Magento-Tags-Pattern or X-Pool header required"));
    }
    if (req.http.X-Magento-Tags-Pattern) {
      ban("obj.http.X-Magento-Tags ~ " + req.http.X-Magento-Tags-Pattern);
    }
# ban everything to catch assets as well
    if (req.http.X-Magento-Tags-Pattern == ".*") {
      ban("req.url ~ .*");
    }
    if (req.http.X-Pool) {
      ban("obj.http.X-Pool ~ " + req.http.X-Pool);
    }
    return (synth(200, "Purged"));
}

if (req.http.X-Forwarded-Proto !~ "https") {
    set req.http.location = "https://" + req.http.host + req.url;
    return (synth(750, "Permanently moved"));
}

if (req.method != "GET" &&
    req.method != "HEAD" &&
    req.method != "PUT" &&
    req.method != "POST" &&
    req.method != "TRACE" &&
    req.method != "OPTIONS" &&
    req.method != "DELETE") {
      /* Non-RFC2616 or CONNECT which is weird. */
      return (pipe);
}

# We only deal with GET and HEAD by default
if (req.method != "GET" && req.method != "HEAD") {
    return (pass);
}

# Bypass customer, shopping cart, checkout
if (req.url ~ "/customer" || req.url ~ "/checkout" || req.url ~ "/payment" || req.url ~ "/reclaim" || req.url ~ "/search" || req.url ~ "/order-confirmation-summary") {
    return (pass);
}

# Bypass health check requests
if (req.url ~ "/health_check.php") {
    return (pass);
}

# Set initial grace period usage status
set req.http.grace = "none";

# normalize url in case of leading HTTP scheme and domain
set req.url = regsub(req.url, "^http[s]?://", "");

# collect all cookies
std.collect(req.http.Cookie);

# Compression filter. See https://www.varnish-cache.org/trac/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
    if (req.url ~ ".(jpg|jpeg|webp|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf|flv|webp)$") {
        # No point in compressing these
        unset req.http.Accept-Encoding;
    } elsif (req.http.Accept-Encoding ~ "gzip") {
        set req.http.Accept-Encoding = "gzip";
    } elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") {
        set req.http.Accept-Encoding = "deflate";
    } else {
        # unknown algorithm
        unset req.http.Accept-Encoding;
    }
}

# Remove all marketing get parameters to minimize the cache objects
if (req.url ~ "(?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=") {
    set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=[-_A-z0-9+()%.]+&?", "");
    set req.url = regsub(req.url, "[?|&]+$", "");
}

# Static files caching
if (req.url ~ "^/(pub/)?(media|static)/") {
    # Static files should not be cached by default
    return (pass);

    # But if you use a few locales and don't use CDN you can enable caching static files by commenting previous line (#return (pass);) and uncommenting next 3 lines
    #unset req.http.Https;
    #unset req.http.X-Forwarded-Proto;
    #unset req.http.Cookie;
}

# Authenticated GraphQL requests should not be cached by default
if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") {
    return (pass);
}

return (hash);
}


sub vcl_hash {
    if ((req.url !~ "/graphql" || !req.http.X-Magento-Cache-Id) && req.http.cookie ~ "X-Magento-Vary=") {
    hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "1"));
}

# For multi site configurations to not cache each other's content
if (req.http.host) {
    hash_data(req.http.host);
} else {
    hash_data(server.ip);
}

# To make sure http users don't see ssl warning
if (req.http.X-Forwarded-Proto) {
    hash_data(req.http.X-Forwarded-Proto);
}

if (req.http.cookie ~ "currency=") {
set req.http.X-TMP = regsub(req.http.cookie, ".*currency=([^;]+);.*","1");
hash_data(req.http.X-TMP);
unset req.http.X-TMP;
}

if (req.url ~ "/graphql") {
    call process_graphql_headers;
}
}

sub process_graphql_headers {
if (req.http.X-Magento-Cache-Id) {
    hash_data(req.http.X-Magento-Cache-Id);

    # When the frontend stops sending the auth token, make sure users stop getting results cached for logged-in users
    if (req.http.Authorization ~ "^Bearer") {
        hash_data("Authorized");
    }
}

if (req.http.Store) {
    hash_data(req.http.Store);
}
if (req.http.Content-Currency) {
    hash_data(req.http.Content-Currency);
}
}

sub vcl_backend_response {
set beresp.grace = 3d;

if (beresp.http.content-type ~ "text") {
    set beresp.do_esi = true;
}

if (bereq.url ~ ".js$" || beresp.http.content-type ~ "text") {
    set beresp.do_gzip = true;
}

if (beresp.http.X-Magento-Debug) {
    set beresp.http.X-Magento-Cache-Control = beresp.http.Cache-Control;
}

# cache only successfully responses and 404s
if (beresp.status != 200 && beresp.status != 404) {
    set beresp.ttl = 0s;
    set beresp.uncacheable = true;
    return (deliver);
} elsif (beresp.http.Cache-Control ~ "private") {
    set beresp.uncacheable = true;
    set beresp.ttl = 86400s;
    return (deliver);
}

# validate if we need to cache it and prevent from setting cookie
if (beresp.ttl > 0s && (bereq.method == "GET" || bereq.method == "HEAD")) {
    unset beresp.http.set-cookie;
}

   # If page is not cacheable then bypass varnish for 2 minutes as Hit-For-Pass
   if (beresp.ttl <= 0s ||
       beresp.http.Surrogate-control ~ "no-store" ||
       (!beresp.http.Surrogate-Control &&
       beresp.http.Cache-Control ~ "no-cache|no-store") ||
       beresp.http.Vary == "*") {
        # Mark as Hit-For-Pass for the next 2 minutes
        set beresp.ttl = 120s;
        set beresp.uncacheable = true;
    }

return (deliver);
}

sub vcl_deliver {
if (resp.http.X-Magento-Debug) {
    if (resp.http.x-varnish ~ " ") {
        set resp.http.X-Magento-Cache-Debug = "HIT";
        set resp.http.Grace = req.http.grace;
    } else {
        set resp.http.X-Magento-Cache-Debug = "MISS";
    }
} else {
    unset resp.http.Age;
}

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";
}

# Not letting browser to cache non-static files.
if (resp.http.Cache-Control !~ "private" && req.url !~ "^/(pub/)?(media|static)/") {
    set resp.http.Pragma = "no-cache";
    set resp.http.Expires = "-1";
    set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
}

unset resp.http.X-Magento-Debug;
unset resp.http.X-Magento-Tags;
unset resp.http.X-Powered-By;
unset resp.http.Server;
unset resp.http.X-Varnish;
unset resp.http.Via;
unset resp.http.Link;
}

sub vcl_synth {
if (resp.status == 750) {
    set resp.http.location = req.http.location;
    set resp.status = 301;
    return (deliver);
}
}

sub vcl_hit {
if (obj.ttl >= 0s) {
    # Hit within TTL period
    return (deliver);
}
if (std.healthy(req.backend_hint)) {
    if (obj.ttl + 300s > 0s) {
        # Hit after TTL expiration, but within grace period
        set req.http.grace = "normal (healthy server)";
        return (deliver);
    } else {
        # Hit after TTL and grace expiration
        return (restart);
    }
} else {
    # server is not healthy, retrieve from cache
    set req.http.grace = "unlimited (unhealthy server)";
    return (deliver);
}
}

We need to focus on domain2, the Cache-Control headers in response headers are max-age=0, no-cache, no-store. No custom code has cacheable=false in Magento’s app/code directory. Hence, I don’t know how is this being added.

I commented the following lines in vcl_backend_response and was able to see X-Cache: HIT on the browser only for the / home page. Other page’s don’t have this parameter at all.

 if (beresp.ttl <= 0s ||
       beresp.http.Surrogate-control ~ "no-store" ||
       (!beresp.http.Surrogate-Control &&
       beresp.http.Cache-Control ~ "no-cache|no-store") ||
       beresp.http.Vary == "*") {
        # Mark as Hit-For-Pass for the next 2 minutes
        set beresp.ttl = 120s;
        set beresp.uncacheable = true;
    }

Upon checking the logs for command:

varnishlog -g request -b -i BeReqUrl -I BerespHeader:Cache-Control 
    -I BerespHeader:Expires -I BerespHeader:Vary -I BerespHeader:Set-Cookie 
    -I BerespHeader:Surrogate-Control -i TTL -q "TTL[6] eq 'uncacheable'"

I get following result for the main domain/home page. (https://www.domain2.com).

**  << BeReq    >> 65569     
--  BereqURL       /
--  BerespHeader   Cache-Control: max-age=0, no-cache, s-maxage=60
--  TTL            RFC 60 10 0 1724061342 1724061342 1724061342 0 60 cacheable
--  TTL            VCL 60 259200 0 1724061342 cacheable
--  TTL            VCL 120 259200 0 1724061342 cacheable
--  TTL            VCL 120 259200 0 1724061342 uncacheable
--  BerespHeader   Vary: Accept-Encoding

Also, there are no logs if I click on another category page or product, it’s as there was never made a request and x-cache is also missing in response headers. Probably all other pages/paths are being bypassed.

Update:

Below are my transaction logs for homepage on refresh.

*   << Request  >> 5         
-   Begin          req 4 rxreq
-   Timestamp      Start: 1724150405.424135 0.000000 0.000000
-   Timestamp      Req: 1724150405.424135 0.000000 0.000000
-   VCL_use        boot
-   ReqStart       127.0.0.1 35242 a0
-   ReqMethod      GET
-   ReqURL         /
-   ReqProtocol    HTTP/1.0
-   ReqHeader      Host: www.domain2.com
-   ReqHeader      X-Forwarded-Host: www.domain2.com
-   ReqHeader      X-Real-IP: 1.1.1.1
-   ReqHeader      X-Forwarded-For: 1.1.1.1, 1.1.1.1
-   ReqHeader      Ssl-Offloaded: 1
-   ReqHeader      X-Forwarded-Port: 443
-   ReqHeader      X-Forwarded-Proto: https
-   ReqHeader      X-Forwarded-Ssl: on
-   ReqHeader      Connection: close
-   ReqHeader      cdn-loop: cloudflare
-   ReqHeader      cf-ipcountry: PK
-   ReqHeader      accept-encoding: gzip, br
-   ReqHeader      cf-ray: 8b61c9c7cf07be84-SIN
-   ReqHeader      cf-visitor: {"scheme":"https"}
-   ReqHeader      cache-control: max-age=0
-   ReqHeader      sec-ch-ua: "Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"
-   ReqHeader      accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
-   ReqHeader      sec-ch-ua-mobile: ?0
-   ReqHeader      upgrade-insecure-requests: 1
-   ReqHeader      user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
-   ReqHeader      sec-ch-ua-platform: "Linux"
-   ReqHeader      sec-fetch-site: same-origin
-   ReqHeader      sec-fetch-mode: navigate
-   ReqHeader      sec-fetch-dest: empty
-   ReqHeader      accept-language: en-US,en;q=0.9
-   ReqHeader      priority: u=0, i
-   ReqHeader      cf-connecting-ip: 1.1.1.1
-   ReqHeader      cookie: ajs_anonymous_id=0ef0-485a-ae6a-d03d649b79e6; _gcl_au=1.1.1724011616; _fbp=fb.1.1724011616211; _hjSessionUser_3574595=56yfdvdvdvgfrvvd
-   ReqUnset       X-Forwarded-For: 1.1.1.1, 1.1.1.1
-   ReqHeader      X-Forwarded-For: 1.1.1.1, 1.1.1.1, 127.0.0.1
-   VCL_call       RECV
-   ReqHeader      PS-CapabilityList: ll,ii,dj,jw,ws:
-   ReqHeader      grace: none
-   ReqURL         /
-   ReqUnset       accept-encoding: gzip, br
-   ReqHeader      Accept-Encoding: gzip
-   VCL_return     hash
-   VCL_call       HASH
-   VCL_return     lookup
-   HitMiss        3 115.365105
-   VCL_call       MISS
-   VCL_return     fetch
-   Link           bereq 6 fetch
-   Timestamp      Fetch: 1724150405.585118 0.160983 0.160983
-   RespProtocol   HTTP/1.1
-   RespStatus     200
-   RespReason     OK
-   RespHeader     Server: nginx
-   RespHeader     Content-Type: text/html;charset=UTF-8
-   RespHeader     X-Content-Type-Options: nosniff
-   RespHeader     Date: Tue, 20 Aug 2024 10:40:05 GMT
-   RespHeader     X-Page-Speed: ngx_pagespeed
-   RespHeader     Link: </RootCmp_CMS_PAGE__default.9b247fcd07254236f444.js>; rel=preload; as=script; nopush
-   RespHeader     Link: </RootCmp_CMS_PAGE__default.9b247fcd07254236f444.js.gz>; rel=preload; as=script; nopush
-   RespHeader     Link: </runtime.8f084be222019779ec78.js>; rel=preload; as=script; nopush
-   RespHeader     Link: </vendors.2510f870ac43c344ee54.js>; rel=preload; as=script; nopush
-   RespHeader     Link: </client.7a54ea122b5c72f16096.js>; rel=preload; as=script; nopush
-   RespHeader     Cache-Control: max-age=0, no-cache
-   RespHeader     Content-Encoding: gzip
-   RespHeader     Vary: Accept-Encoding
-   RespHeader     X-Varnish: 5
-   RespHeader     Age: 0
-   RespHeader     Via: 1.1 varnish (Varnish/6.5)
-   VCL_call       DELIVER
-   RespUnset      Age: 0
-   RespHeader     X-Cache: MISS
-   RespHeader     Pragma: no-cache
-   RespHeader     Expires: -1
-   RespUnset      Cache-Control: max-age=0, no-cache
-   RespHeader     Cache-Control: no-store, no-cache, must-revalidate, max-age=0
-   RespUnset      Server: nginx
-   RespUnset      X-Varnish: 5
-   RespUnset      Via: 1.1 varnish (Varnish/6.5)
-   RespUnset      Link: </RootCmp_CMS_PAGE__default.9b247fcd07254236f444.js>; rel=preload; as=script; nopush
-   RespUnset      Link: </RootCmp_CMS_PAGE__default.9b247fcd07254236f444.js.gz>; rel=preload; as=script; nopush
-   RespUnset      Link: </runtime.8f084be222019779ec78.js>; rel=preload; as=script; nopush
-   RespUnset      Link: </vendors.2510f870ac43c344ee54.js>; rel=preload; as=script; nopush
-   RespUnset      Link: </client.7a54ea122b5c72f16096.js>; rel=preload; as=script; nopush
-   VCL_return     deliver
-   Timestamp      Process: 1724150405.585169 0.161034 0.000051
-   Filters        
-   RespHeader     Accept-Ranges: bytes
-   RespHeader     Content-Length: 72801
-   RespHeader     Connection: close
-   Timestamp      Resp: 1724150405.585305 0.161170 0.000135
-   ReqAcct        2066 0 2066 377 72801 73178
-   End            
**  << BeReq    >> 6         
--  Begin          bereq 5 fetch
--  VCL_use        boot
--  Timestamp      Start: 1724150405.424291 0.000000 0.000000
--  BereqMethod    GET
--  BereqURL       /
--  BereqProtocol  HTTP/1.0
--  BereqHeader    Host: www.domain2.com
--  BereqHeader    X-Forwarded-Host: www.domain2.com
--  BereqHeader    X-Real-IP: 1.1.1.1
--  BereqHeader    Ssl-Offloaded: 1
--  BereqHeader    X-Forwarded-Port: 443
--  BereqHeader    X-Forwarded-Proto: https
--  BereqHeader    X-Forwarded-Ssl: on
--  BereqHeader    cdn-loop: cloudflare
--  BereqHeader    cf-ipcountry: PK
--  BereqHeader    cf-ray: 8b61c9c7cf07be84-SIN
--  BereqHeader    cf-visitor: {"scheme":"https"}
--  BereqHeader    sec-ch-ua: "Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"
--  BereqHeader    accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
--  BereqHeader    sec-ch-ua-mobile: ?0
--  BereqHeader    upgrade-insecure-requests: 1
--  BereqHeader    user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
--  BereqHeader    sec-ch-ua-platform: "Linux"
--  BereqHeader    sec-fetch-site: same-origin
--  BereqHeader    sec-fetch-mode: navigate
--  BereqHeader    sec-fetch-dest: empty
--  BereqHeader    accept-language: en-US,en;q=0.9
--  BereqHeader    priority: u=0, i
--  BereqHeader    cf-connecting-ip: 1.1.1.1
--  BereqHeader    cookie: ajs_anonymous_id=0ef0-485a-ae6a-d03d649b79e6; _gcl_au=1.1.1777705759; _fbp=fb.1.1724011616211; _hjSessionUser_3574595=242424v24vhvgv
--  BereqHeader    X-Forwarded-For: 1.1.1.1, 1.1.1.1, 127.0.0.1
--  BereqHeader    PS-CapabilityList: ll,ii,dj,jw,ws:
--  BereqHeader    grace: none
--  BereqHeader    Accept-Encoding: gzip
--  BereqProtocol  HTTP/1.1
--  BereqHeader    X-Varnish: 6
--  VCL_call       BACKEND_FETCH
--  VCL_return     fetch
--  BackendOpen    29 domain2 127.0.0.1 8084 127.0.0.1 33002 reuse
--  Timestamp      Bereq: 1724150405.424392 0.000100 0.000100
--  Timestamp      Beresp: 1724150405.580200 0.155909 0.155808
--  BerespProtocol HTTP/1.1
--  BerespStatus   200
--  BerespReason   OK
--  BerespHeader   Server: nginx
--  BerespHeader   Content-Type: text/html;charset=UTF-8
--  BerespHeader   Transfer-Encoding: chunked
--  BerespHeader   Connection: keep-alive
--  BerespHeader   X-Content-Type-Options: nosniff
--  BerespHeader   Date: Tue, 20 Aug 2024 10:40:05 GMT
--  BerespHeader   X-Page-Speed: ngx_pagespeed
--  BerespHeader   Link: </RootCmp_CMS_PAGE__default.9b247fcd07254236f444.js>; rel=preload; as=script; nopush
--  BerespHeader   Link: </RootCmp_CMS_PAGE__default.9b247fcd07254236f444.js.gz>; rel=preload; as=script; nopush
--  BerespHeader   Link: </runtime.8f084be222019779ec78.js>; rel=preload; as=script; nopush
--  BerespHeader   Link: </vendors.2510f870ac43c344ee54.js>; rel=preload; as=script; nopush
--  BerespHeader   Link: </client.7a54ea122b5c72f16096.js>; rel=preload; as=script; nopush
--  BerespHeader   Cache-Control: max-age=0, no-cache
--  TTL            RFC 0 10 0 1724150406 1724150406 1724150405 0 0 cacheable
--  VCL_call       BACKEND_RESPONSE
--  TTL            VCL 0 259200 0 1724150406 cacheable
--  TTL            VCL 120 259200 0 1724150406 cacheable
--  TTL            VCL 120 259200 0 1724150406 uncacheable
--  VCL_return     deliver
--  Filters         esi_gzip
--  BerespHeader   Content-Encoding: gzip
--  BerespHeader   Vary: Accept-Encoding
--  Storage        malloc Transient
--  Fetch_Body     2 chunked -
--  Gzip           G F E 98588 72801 80 582328 582338
--  BackendClose   29 domain2 recycle
--  Timestamp      BerespBody: 1724150405.585110 0.160819 0.004910
--  Length         72801
--  BereqAcct      2091 0 2091 668 0 668
--  End