Shopware HTTP Cache with Varnish
Shopware provides a complete configuration guide for Varnish in the Shopware Developer Docs:
Trusted Proxies
Using Symfony and Varnish is not a problem in most cases. However, if a request passes through a proxy, certain request data is sent using either the standard Forwarded or X-Forwarded header. For example, instead of reading the REMOTE_ADDR header (which now contains the IP address of your reverse proxy), the user’s actual IP address is stored.
If you do not configure Symfony to check 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 are 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 proxies.
For more information, see:
Reverse Proxy Configuration
To ensure that Shopware 6 sets the correct Cache-Control header, you must configure the reverse proxy. The following configuration ensures that Shopware sets the Cache-Control header value to public, must-revalidate 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"
# Maximum number of parallel invalidations at the 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" Starting with 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"
# Maximum number of parallel invalidations at the 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" Note: 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 configured accordingly before upgrading.
Please ensure that you specify the correct IP addresses for the Varnish instances, including the HTTP ports if they differ from the default port 80. In the default configuration of the creoline Varnish server, the HTTP port 80 is configured here. If you have installed Varnish independently, the default port is 6081.
Note that, by default, the HTTP Host header in the BAN request corresponds to the IP addresses or hostnames of the specified hosts. By specifying ban_headers, the Host header can be overridden accordingly so that the correct cache key can be determined in Varnish.
Varnish Configuration
The Varnish configuration can be edited and deployed 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 changes to the Varnish configuration immediately triggers a config test followed by a reload of the Varnish instance. If you are configuring Varnish for the first time, we recommend using a test instance to verify that the configuration is correct.
Example configuration with soft purge:
vcl 4.0;
import std;
import purge;
# You should specify all your app nodes here and use round-robin to select a backend
backend default {
.host = "<app-host>";
.port = "80";
}
# ACL for purgers' IPs. (This must include app server IPs)
acl purgers {
"127.0.0.1";
"localhost";
"::1";
}
sub vcl_recv {
# Mitigate the HTTP proxy application vulnerability; see: https://httpoxy.org/
unset req.http.Proxy;
# Strip query strings only needed by browser JavaScript. Customize based on the tags used.
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 RFC 3986, Section 2.3, "Unreserved Characters," for the 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);
# Ensure that the client's IP address is passed 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 unusual. */
return (pipe);
}
# By default, we only handle GET and HEAD requests
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);
}
# Remove the cookie before the image is added to the 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 added 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 the link header if the session has 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 added 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 for internal use only
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
By default, the Shopware HTTP cache is only available for non-logged-in users and visitors without shopping cart contents. If you do not use any customizations for logged-in users, the HTTP cache can also be enabled for logged-in users or visitors with shopping carts.
# config/packages/prod/shopware.yaml
shopware:
cache:
invalidation:
http_cache: []