diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index ee89dc8d22d..d283339d38b 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -1,5 +1,5 @@ -# VCL version 5.0 is not supported so it should be 4.0 even though actually used Varnish version is 6 -vcl 4.0; +# The VCL version is not related to the version of Varnish. Use VCL version 4.1 to enforce the use of Varnish 6 or later +vcl 4.1; import std; # The minimal Varnish version is 6.0 @@ -23,8 +23,31 @@ acl purge { } sub vcl_recv { - if (req.restarts > 0) { - set req.hash_always_miss = true; + # Remove empty query string parameters + # e.g.: www.example.com/index.html? + if (req.url ~ "\?$") { + set req.url = regsub(req.url, "\?$", ""); + } + + # Remove port number from host header + set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); + + # Sorts query string parameters alphabetically for cache normalization purposes + set req.url = std.querysort(req.url); + + # Remove the proxy header to mitigate the httpoxy vulnerability + # See https://httpoxy.org/ + unset req.http.proxy; + + # Add X-Forwarded-Proto header when using https + if (!req.http.X-Forwarded-Proto && (std.port(server.ip) == 443 || std.port(server.ip) == 8443)) { + set req.http.X-Forwarded-Proto = "https"; + } + + # Reduce grace to the configured setting if the backend is healthy + # In case of an unhealthy backend, the original grace is used + if (std.healthy(req.backend_hint)) { + set req.http.grace = /* {{ grace_period }} */s; } if (req.method == "PURGE") { @@ -35,7 +58,7 @@ sub vcl_recv { # has been added to the response in your backend server config. This is used, for example, by the # 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")); + return (purge); } if (req.http.X-Magento-Tags-Pattern) { ban("obj.http.X-Magento-Tags ~ " + req.http.X-Magento-Tags-Pattern); @@ -50,10 +73,10 @@ sub vcl_recv { req.method != "HEAD" && req.method != "PUT" && req.method != "POST" && + req.method != "PATCH" && req.method != "TRACE" && req.method != "OPTIONS" && req.method != "DELETE") { - /* Non-RFC2616 or CONNECT which is weird. */ return (pipe); } @@ -72,33 +95,12 @@ sub vcl_recv { 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 + # Collapse multiple cookie headers into one 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|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf|flv)$") { - # 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+()%.]+&?", ""); + if (req.url ~ "(\?|&)(gclid|cx|_kx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=") { + set req.url = regsuball(req.url, "(gclid|cx|_kx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); set req.url = regsub(req.url, "[?|&]+$", ""); } @@ -126,96 +128,64 @@ sub vcl_hash { hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1")); } - # To make sure http users don't see ssl warning - if (req.http./* {{ ssl_offloaded_header }} */) { - hash_data(req.http./* {{ ssl_offloaded_header }} */); - } + hash_data(req.http./* {{ ssl_offloaded_header }} */); + /* {{ design_exceptions_code }} */ if (req.url ~ "/graphql") { - call process_graphql_headers; - } -} + if (req.http.X-Magento-Cache-Id) { + hash_data(req.http.X-Magento-Cache-Id); -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"); + # 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"); + } + } else { + hash_data(req.http.Store); + hash_data(req.http.Content-Currency); } } - - if (req.http.Store) { - hash_data(req.http.Store); - } - - if (req.http.Content-Currency) { - hash_data(req.http.Content-Currency); - } } sub vcl_backend_response { - + # Serve stale content for three days after object expiration + # Perform asynchronous revalidation while stale content is served set beresp.grace = 3d; + # All text-based content can be parsed as ESI if (beresp.http.content-type ~ "text") { set beresp.do_esi = true; } + # Allow GZIP compression on all JavaScript files and all text-based content if (bereq.url ~ "\.js$" || beresp.http.content-type ~ "text") { set beresp.do_gzip = true; } + # Add debug headers if (beresp.http.X-Magento-Debug) { set beresp.http.X-Magento-Cache-Control = beresp.http.Cache-Control; } - # cache only successfully responses and 404s that are not marked as private - if (beresp.status != 200 && - beresp.status != 404 && - beresp.http.Cache-Control ~ "private") { + # Only cache HTTP 200 and HTTP 404 responses + if (beresp.status != 200 && beresp.status != 404) { + set beresp.ttl = 120s; 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")) { - # Collapse beresp.http.set-cookie in order to merge multiple set-cookie headers - # Although it is not recommended to collapse set-cookie header, - # it is safe to do it here as the set-cookie header is removed below - std.collect(beresp.http.set-cookie); - # Do not cache the response under current cache key (hash), - # if the response has X-Magento-Vary but the request does not. - if ((bereq.url !~ "/graphql" || !bereq.http.X-Magento-Cache-Id) - && bereq.http.cookie !~ "X-Magento-Vary=" - && beresp.http.set-cookie ~ "X-Magento-Vary=") { - set beresp.ttl = 0s; - set beresp.uncacheable = true; - } - unset beresp.http.set-cookie; + # Don't cache if the request cache ID doesn't match the response cache ID for graphql requests + if (bereq.url ~ "/graphql" && bereq.http.X-Magento-Cache-Id && bereq.http.X-Magento-Cache-Id != beresp.http.X-Magento-Cache-Id) { + set beresp.ttl = 120s; + set beresp.uncacheable = true; + return (deliver); } - # 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; - } - - # If the cache key in the Magento response doesn't match the one that was sent in the request, don't cache under the request's key - if (bereq.url ~ "/graphql" && bereq.http.X-Magento-Cache-Id && bereq.http.X-Magento-Cache-Id != beresp.http.X-Magento-Cache-Id) { - set beresp.ttl = 0s; - set beresp.uncacheable = true; - } - - return (deliver); + # Remove the Set-Cookie header for cacheable content + # Only for HTTP GET & HTTP HEAD requests + if (beresp.ttl > 0s && (bereq.method == "GET" || bereq.method == "HEAD")) { + unset beresp.http.Set-Cookie; + } } sub vcl_deliver {