Shopware HTTP Cache with Varnish
Shopware provides a complete configuration for the configuration of Varnish in the Shopware Developer Docs:
Trusted Proxies
The use of Symfony and Varnish is not a problem in most cases. However, when a request goes through a proxy, certain request data is sent with either the default Forwarded
or X-Forwarded
header. For example, instead of reading the REMOTE_ADDR
header (which is now the IP address of your reverse proxy), the actual IP address of the user is stored.
If you do not configure Symfony to look for these headers, you will receive incorrect information about the client's IP address. Regardless of whether the client connects via https or not, the client's port and hostname will be queried.
Example configuration via .env
:
TRUSTED_PROXIES=127.0.0.1,127.0.0.2,10.20.1.1/32,10.20.0.1/32,10.20.0.2/32
In this example configuration, the two Varnish instances 10.20.0.1
and 10.20.0.2
are authorized as a proxy.
You can find more information on this under:
Reverse proxy configuration
In order for Shopware 6 to set the correct Cache-Control
header, the configuration of the reverse proxy is required. The following configuration can be used to ensure that Shopware sets the value public, must-revalidate
instead of the Cache-Control
header value private, no-cache
and invalidates the caches accordingly in Varnish via BAN
requests.
To do this, create the following file:
config/packages/storefront.yaml
Up to Shopware 6.5.X.X
storefront:
reverse_proxy:
enabled: true
ban_method: "BAN"
# Varnish hosts (IP:Port) [Default Port: 80]
hosts: [ "http://10.20.X.X", "http://10.20.X.X" ]
# Additional Headers for BAN Requests (E.G. Overwrite Host Header)
ban_headers:
Host: "www.creoline-demo.com"
# Max parallel invalidations at same time for a single worker
max_parallel_invalidations: 5
# Redis (Default DB: 0, Use /X for another database)
redis_url: "redis://10.20.X.X:6379/0"
From Shopware 6.6.0.0
shopware:
http_cache:
reverse_proxy:
enabled: true
ban_method: "BAN"
# Varnish hosts (IP:Port) [Default Port: 80]
hosts: [ "http://10.20.X.X", "http://10.20.X.X" ]
# Additional Headers for BAN Requests (E.G. Overwrite Host Header)
ban_headers:
Host: "www.creoline-demo.com"
# Max parallel invalidations at same time for a single worker
max_parallel_invalidations: 5
# Redis (Default DB: 0, Use /X for another database)
redis_url: "redis://10.20.X.X:6379/0"
Attention: Shopware has been using a different YAML schema since version 6.6.0.0. If you are already using Shopware with Varnish in an earlier version, the new schema should be stored accordingly before the upgrade.
Please ensure that you enter the correct IP addresses of the Varnish instances, including the HTTP ports, if this differs from the standard port 80. In the standard configuration of the creoline Varnish server, the HTTP port 80
is configured here. If you have installed Varnish independently, the default port is 6081
.
Please note that the HTTP Host
header in the BAN
request corresponds to the IP addresses or host names of the specified hosts
by default. By specifying ban_headers
, the host
header can be overwritten accordingly so that the correct cache key can be determined in Varnish.
Varnish configuration
The Varnish configuration can be edited and rolled out directly via the configuration module in the Customer Center. Navigate to Server → Your server → Configuration files → Varnish Config, to customize the Varnish configuration.
Please note that saving the changes to the Varnish configuration immediately triggers a config test with a subsequent reload of the Varnish instance. If you configure Varnish inital, we recommend a test instance to evaluate the correct configuration.
Example configuration with Soft-Purge:
vcl 4.0;
import std;
import purge;
# You should specify here all your app nodes and use round robin to select a backend
backend default {
.host = "<app-host>";
.port = "80";
}
# ACL for purgers IP. (This needs to contain app server ips)
acl purgers {
"127.0.0.1";
"localhost";
"::1";
}
sub vcl_recv {
# Mitigate httpoxy application vulnerability, see: https://httpoxy.org/
unset req.http.Proxy;
# Strip query strings only needed by browser javascript. Customize to used tags.
if (req.url ~ "(\?|&)(pk_campaign|piwik_campaign|pk_kwd|piwik_kwd|pk_keyword|pixelId|kwid|kw|adid|chl|dv|nk|pa|camid|adgid|cx|ie|cof|siteurl|utm_[a-z]+|_ga|gclid)=") {
# see rfc3986#section-2.3 "Unreserved Characters" for regex
set req.url = regsuball(req.url, "(pk_campaign|piwik_campaign|pk_kwd|piwik_kwd|pk_keyword|pixelId|kwid|kw|adid|chl|dv|nk|pa|camid|adgid|cx|ie|cof|siteurl|utm_[a-z]+|_ga|gclid)=[A-Za-z0-9\-\_\.\~]+&?", "");
}
set req.url = regsub(req.url, "(\?|\?&|&&)$", "");
# Normalize query arguments
set req.url = std.querysort(req.url);
# Make sure that the client ip is forward to the client.
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;
}
# Handle BAN
if (req.method == "BAN") {
if (!client.ip ~ purgers) {
return (synth(405, "Method not allowed"));
}
return (hash);
}
# Normalize Accept-Encoding header
# straight from the manual: https://www.varnish-cache.org/docs/3.0/tutorial/vary.html
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg)$") {
# 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") {
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm
unset req.http.Accept-Encoding;
}
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PATCH" &&
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);
}
# Don't cache Authenticate & Authorization
if (req.http.Authenticate || req.http.Authorization) {
return (pass);
}
# Always pass these paths directly to php without caching
# Note: virtual URLs might bypass this rule (e.g. /en/checkout)
if (req.url ~ "^/(checkout|account|admin|api)(/.*)?$") {
return (pass);
}
return (hash);
}
sub vcl_hash {
# Consider Shopware HTTP cache cookies
if (req.http.cookie ~ "sw-cache-hash=") {
hash_data("+context=" + regsub(req.http.cookie, "^.*?sw-cache-hash=([^;]*);*.*$", "\1"));
} elseif (req.http.cookie ~ "sw-currency=") {
hash_data("+currency=" + regsub(req.http.cookie, "^.*?sw-currency=([^;]*);*.*$", "\1"));
}
}
sub vcl_hit {
if (req.method == "BAN") {
call soft_purge_page;
}
# Consider client states for response headers
if (req.http.cookie ~ "sw-states=") {
set req.http.states = regsub(req.http.cookie, "^.*?sw-states=([^;]*);*.*$", "\1");
if (req.http.states ~ "logged-in" && obj.http.sw-invalidation-states ~ "logged-in" ) {
return (pass);
}
if (req.http.states ~ "cart-filled" && obj.http.sw-invalidation-states ~ "cart-filled" ) {
return (pass);
}
}
}
sub vcl_miss {
if (req.method == "BAN") {
call soft_purge_page;
}
}
sub vcl_backend_response {
# Fix Vary Header in some cases
# https://www.varnish-cache.org/trac/wiki/VCLExampleFixupVary
if (beresp.http.Vary ~ "User-Agent") {
set beresp.http.Vary = regsub(beresp.http.Vary, ",? *User-Agent *", "");
set beresp.http.Vary = regsub(beresp.http.Vary, "^, *", "");
if (beresp.http.Vary == "") {
unset beresp.http.Vary;
}
}
# Respect the Cache-Control=private header from the backend
if (
beresp.http.Pragma ~ "no-cache" ||
beresp.http.Cache-Control ~ "no-cache" ||
beresp.http.Cache-Control ~ "private"
) {
set beresp.ttl = 0s;
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
set beresp.uncacheable = true;
return (deliver);
}
# strip the cookie before the image is inserted into cache.
if (bereq.url ~ "\.(png|gif|jpg|swf|css|js|webp)$") {
unset beresp.http.set-cookie;
}
# Allow items to be stale if needed.
set beresp.grace = 6h;
# Save the bereq.url so bans work efficiently
set beresp.http.x-url = bereq.url;
set beresp.http.X-Cacheable = "YES";
# Remove the exact PHP version from the response for more security
unset beresp.http.x-powered-by;
return (deliver);
}
sub vcl_deliver {
## we don't want the client to cache
set resp.http.Cache-Control = "max-age=0, private";
# remove link header, if session is already started to save client resources
if (req.http.cookie ~ "session-") {
unset resp.http.Link;
}
# Set a cache header to allow us to inspect the response headers during testing
if (obj.hits > 0) {
unset resp.http.set-cookie;
set resp.http.X-Cache = "HIT";
if (obj.ttl <= 0s && obj.grace > 0s) {
set resp.http.X-Cache = "STALE";
}
} else {
set resp.http.X-Cache = "MISS";
}
# Remove the exact PHP version from the response for more security (e.g. 404 pages)
unset resp.http.x-powered-by;
# Remove additional information about Varnish
unset resp.http.via;
unset resp.http.x-varnish;
# invalidation headers are only for internal use
unset resp.http.sw-invalidation-states;
set resp.http.X-Cache-Hits = obj.hits;
set resp.http.X-Cache-Lifetime = obj.ttl;
}
sub soft_purge_page {
# See https://docs.varnish-software.com/varnish-cache-plus/vmods/purge/ for all possible options
set req.http.purged = purge.soft(ttl = 0s, grace = 300s, keep = 3600s);
return (synth(200));
}
HTTP cache for logged-in users or visitors with shopping carts
In the default setting, the Shopware HTTP cache is only available for users who are not logged in and visitors without shopping cart content. If you do not use customizations for logged-in users, the HTTP cache can also be activated for logged-in users or visitors with shopping carts.
# config/packages/prod/shopware.yaml
shopware:
cache:
invalidation:
http_cache: []