Varnish¶
Installation¶
To install the latest version of Varnish Cache (the high-performance HTTP accelerator), we need to add the official Varnish repository.
First we make sure we have a few dependencies:
sudo -s
apt update && apt install debian-archive-keyring curl gnupg apt-transport-https
Then install the GPG key:
curl -L https://packagecloud.io/varnishcache/varnish65/gpgkey | sudo apt-key add -
Create a file named /etc/apt/sources.list.d/varnishcache_varnish65.list that contains the repository configuration below:
deb https://packagecloud.io/varnishcache/varnish65/ubuntu/ focal main
deb-src https://packagecloud.io/varnishcache/varnish65/ubuntu/ focal main
Update apt and install Varnish:
apt update && apt install varnish
By default, Varnish runs with 256mb of RAM allocated for its cache. For my own site, I want to utilise more RAM than this. I also would like to disable gzip in Varnish so that modsecurity can deal with the responses. To do this, edit the systemd config file:
nano /lib/systemd/system/varnish.service
Find the line that says:
-s malloc,256m
and amend to whatever you wish to allocate. For example, to allocate 2GB:
-s malloc,2048m
Also, add an extra parameter to disable gzip so that Modsecurity can read the responses:
-p http_gzip_support=off \
Save the file and reload systemd:
systemctl daemon-reload
Enable the service:
systemctl enable varnish
VCL Files¶
The vcl files are kept in /etc/varnish.
By default, Varnish uses /etc/varnish.default.vcl
To create a new one, back up the original first:
mv /etc/varnish/default.vcl /etc/varnish/default.orig
And create a new /etc/varnish/default.vcl
Further Reading
Here is the VCL file that I use for my Wordpress site:
# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.1 format.
vcl 4.1;
# Remember, you can control the TTL for different content types by setting Cache-Control
# headers in .htaccess files (with mod_expires). Varnish defaults to 2 minutes.
# Helper functions
import std;
import directors;
# Default backend definition. Set this to point to your content server.
backend default {
.host = "127.0.0.1";
.port = "8080";
}
# ACL we'll use later to allow purges
# Only allow purging from specific IPs
acl purge {
"localhost";
"127.0.0.1";
"example.com";
"anotherexample.com";
"::1";
}
sub vcl_recv {
# Called at the beginning of a request, after the complete request has been received and parsed.
# Its purpose is to decide whether or not to serve the request, how to do it, and, if applicable,
# which backend to use. It is also used to modify the request
# The below happens before we check if we have this in cache already.
# Typically you clean up the request here, removing cookies you don't need, rewriting the request, etc.
# Redirect http to https
if (client.ip != "127.0.0.1" && req.http.host ~ "example.com") {
set req.http.x-redir = "https://example.com" + req.url;
return(synth(850, ""));
}
/*
# Blocks
if (req.http.user-agent ~ "^$" && req.http.referer ~ "^$") {
return (synth(204, "No content"));
}
if (req.http.user-agent ~ "(ahrefs|bingbot|domaincrawler|dotbot|mj12bot|semrush)") {
return (synth(204, "Bot blocked"));
}
# If we host multiple domains on a server, here you can list the domains you DO NOT want to cache
# The first check matches both naked & "www" subdomains. Use the second for non generic subdomains.
if (
req.http.host ~ "(www\.)?(domain1.com|domain2.org|domain3.net)" ||
req.http.host ~ "(subdomain.domain4.tld|othersubdomain.domain5.tld)"
) {
return (pass);
}
*/
# Normalize the header if it exists, remove the port (in case you're testing this on various TCP ports)
if (req.http.Host) {
set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
}
# LetsEncrypt Certbot passthrough
if (req.url ~ "^/\.well-known/acme-challenge/") {
return (pass);
}
# Forward client's IP to the backend
if (req.restarts == 0) {
if (req.http.X-Real-IP) {
set req.http.X-Forwarded-For = req.http.X-Real-IP;
} else 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;
}
}
# Normalize the query arguments (but exclude for WordPress' backend)
if (req.url !~ "wp-admin") {
set req.url = std.querysort(req.url);
}
# Non-RFC2616 or CONNECT which is weird.
if (
req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE"
) {
return (pipe);
}
# Remove the proxy header (see httpoxy.org/)
unset req.http.proxy;
# Allow purging
if (req.method == "PURGE") {
if (!client.ip ~ purge) { # purge is the ACL defined at the begining
# Not from an allowed IP? Then die with an error.
return (synth(405, "This IP is not allowed to send PURGE requests."));
}
# If you got this stage (and didn't error out above), purge the cached result
return (purge);
}
# Implementing websocket support (www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
#if (req.http.Upgrade ~ "(?i)websocket") {
# return (pipe);
#}
# We only deal with GET and HEAD by default
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
# === URL manipulation ===
# First remove the Google Analytics added parameters, useless for our backend
if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "");
set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?");
set req.url = regsub(req.url, "\?&", "?");
set req.url = regsub(req.url, "\?$", "");
}
# Strip hash, server doesn't need it.
if (req.url ~ "\#") {
set req.url = regsub(req.url, "\#.*$", "");
}
# Strip a trailing ? if it exists
#if (req.url ~ "\?$") {
# set req.url = regsub(req.url, "\?$", "");
#}
# Allow banning regexes
if (req.method == "BAN") {
if (!client.ip ~ purge) {
return (synth(405, "This IP is not allowed to send BAN requests."));
}
ban("req.http.host == " + req.http.host + " && req.url ~ ^" + req.url);
# Throw a synthetic page so the request won't go to the backend.
return (synth(200, "Ban added"));
}
##Possibility to cache admin-ajax GET requests
if ((req.url ~ "admin-ajax.php") && !req.http.cookie ~ "wordpress_logged_in" ) {
return (hash);
}
# === Generic cookie manipulation ===
# Remove the "has_js" cookie
set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
# Remove any Google Analytics based cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_gat=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", "");
# Remove DoubleClick offensive cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", "");
# Remove the Quant Capital cookies (added by some plugin, all __qca)
set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
# Remove the AddThis cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", "");
# Remove the wp-settings-1 cookie
set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-1=[^;]+(; )?", "");
# Remove the wp-settings-time-1 cookie
set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-time-1=[^;]+(; )?", "");
# Remove the wp test cookie
set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", "");
# Remove a ";" prefix in the cookie if present
set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", "");
# Remove the PHPSESSID in members area cookie
set req.http.Cookie = regsuball(req.http.Cookie, "PHPSESSID=[^;]+(; )?", "");
# Are there cookies left with only spaces or that are empty?
if (req.http.cookie ~ "^\s*$") {
unset req.http.cookie;
}
#if (req.http.Cache-Control ~ "(?i)no-cache") {
#if (client.ip ~ purge) {
# Ignore requests via proxy caches and badly behaved crawlers
# like msnbot that send no-cache with every request.
#if (! (req.http.Via || req.http.User-Agent ~ "(?i)bot" || req.http.X-Purge)) {
#set req.hash_always_miss = true; # Doesn't seems to refresh the object in the cache
#return(purge); # Couple this with restart in vcl_purge and X-Purge header to avoid loops
#}
#}
#}
# Check for the custom "X-Logged-In" header (used by K2 and other apps) to identify
# if the visitor is a guest, then unset any cookie (including session cookies) provided
# it's not a POST request.
if(req.http.X-Logged-In == "False" && req.method != "POST") {
unset req.http.Cookie;
}
# MEGA DROP. Drop ALL cookies sent to WordPress, except those originating from the URLs defined.
# This increases HITs significantly, but be careful it can also break plugins that need cookies.
# Note: The /members/ directory had problems with PMP login and social login plugin.
# Adding it to the exclude list here (and including it below in the "Retain cookies" list) fixed login.
# This works better than than other cookie removal examples found on varnish's website.
# Note phpBB directory (forumPM) also passes cookies here.
if (!(req.url ~ "(wp-login|wp-admin|cart|my-account|checkout|addons|wordpress-social-login|m4rk|wp-login\.php|forumPM|members|&preview=true)")) {
unset req.http.cookie;
}
# === DO NOT CACHE ===
# Don't cache HTTP authorization/authentication pages and pages with certain headers or cookies
if (
req.http.Authorization ||
req.method == "POST" ||
req.http.Authenticate ||
req.http.X-Logged-In == "True" ||
req.http.Cookie ~ "userID" ||
req.http.Cookie ~ "joomla_[a-zA-Z0-9_]+" ||
req.http.Cookie ~ "(wordpress_[a-zA-Z0-9_]+|wp-postpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+)"
) {
#set req.http.Cache-Control = "private, max-age=0, no-cache, no-store";
#set req.http.Expires = "Mon, 01 Jan 2001 00:00:00 GMT";
#set req.http.Pragma = "no-cache";
return (pass);
}
# Exclude the following paths (e.g. backend admins, user pages or ad URLs that require tracking)
# In Joomla specifically, you are advised to create specific entry points (URLs) for users to
# interact with the site (either common user logins or even commenting), e.g. make a menu item
# to point to a user login page (e.g. /login), including all related functionality such as
# password reset, email reminder and so on.
if(
req.url ~ "^/addons" ||
req.url ~ "^/administrator" ||
req.url ~ "^/cart" ||
req.url ~ "^/checkout" ||
req.url ~ "^/component/banners" ||
req.url ~ "^/component/socialconnect" ||
req.url ~ "^/component/users" ||
req.url ~ "^/connect" ||
req.url ~ "^/contact" ||
req.url ~ "^/login" ||
req.url ~ "^/logout" ||
req.url ~ "^/lost-password" ||
req.url ~ "^/my-account" ||
req.url ~ "^/register" ||
req.url ~ "^/signin" ||
req.url ~ "^/signup" ||
req.url ~ "^/wc-api" ||
req.url ~ "^/wp-admin" ||
req.url ~ "^/wp-login.php" ||
req.url ~ "^\?add-to-cart=" ||
req.url ~ "^\?wc-api="
) {
#set req.http.Cache-Control = "private, max-age=0, no-cache, no-store";
#set req.http.Expires = "Mon, 01 Jan 2001 00:00:00 GMT";
#set req.http.Pragma = "no-cache";
return (pass);
}
# Don't cache ajax requests
if(req.http.X-Requested-With == "XMLHttpRequest" || req.url ~ "nocache") {
#set req.http.Cache-Control = "private, max-age=0, no-cache, no-store";
#set req.http.Expires = "Mon, 01 Jan 2001 00:00:00 GMT";
#set req.http.Pragma = "no-cache";
return (pass);
}
# Wordpress: don't cache these special Wordpress pages
if (req.url ~ "/feed(/)?|preview=true|m4rk|wp-(comments-post|cron|activate|mail)\.php|register\.php") {
return (pass);
}
# Wordpress: don't cache search results
if (req.url ~ "/\?s=") {
return (pass);
}
# Wordpress: don't cache REST API (hand-rolled APIs used by custom themes)
if (req.url ~ "/shared-gc/includes/rest-api/") {
return (pass);
}
# Wordpress: don't cache anything with a cache-breaking v=<random> parameter (see gc.loadCachedJSON() JS function)
if (req.url ~ "(\?|&)v=0") {
return (pass);
}
# Don't cache the special pages we use to generate PDFs from the Wordpress catalog site
if (req.url ~ "/generate-catalog/") {
return (pass);
}
# Let preview posts through
if (req.url ~ "preview=true") {
return(pass);
}
# Don't cache sitemaps
if (req.url ~ "/sitemaps") {
return(pass);
}
# Don't cache REST API
if (req.url ~ "/wp-json/") {
return(pass);
}
if (req.url ~ "/index.php/") {
return(pass);
}
# Large static files are delivered directly to the end-user without
# waiting for Varnish to fully read the file first.
# Varnish 4 fully supports Streaming, so set do_stream in vcl_backend_response()
if (req.url ~ "^[^?]*\.(7z|avi|bz2|flac|flv|gz|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|rar|tar|tgz|tbz|txz|wav|webm|wmv|xz|zip)(\?.*)?$") {
unset req.http.Cookie;
return (hash);
}
# === STATIC FILES ===
# Properly handle different encoding types
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf)$") {
# No point in compressing these
unset req.http.Accept-Encoding;
} elseif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elseif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm (aka crappy browser)
unset req.http.Accept-Encoding;
}
}
# Remove all cookies for static files & deliver directly
if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
unset req.http.Cookie;
return (hash);
}
# Send Surrogate-Capability headers to announce ESI support to backend
set req.http.Surrogate-Capability = "key=ESI/1.0";
if (req.http.Authorization) {
# Not cacheable by default
return (pass);
}
# Respect the browser's desire for a fresh copy on hard refresh
# This ban will only work if there are no further URL changes (e.g. set req.url = ...) after it.
if (req.http.Cache-Control == "no-cache") {
ban("req.http.host == " + req.http.host + " && req.url == " + req.url);
}
return (hash);
}
/*
sub vcl_pipe {
# Called upon entering pipe mode.
# In this mode, the request is passed on to the backend, and any further data from both the client
# and backend is passed on unaltered until either end closes the connection. Basically, Varnish will
# degrade into a simple TCP proxy, shuffling bytes back and forth. For a connection in pipe mode,
# no other VCL subroutine will ever get called after vcl_pipe.
# Note that only the first request to the backend will have
# X-Forwarded-For set. If you use X-Forwarded-For and want to
# have it set for all requests, make sure to have:
# set bereq.http.connection = "close";
# here. It is not set by default as it might break some broken web
# applications, like IIS with NTLM authentication.
# set bereq.http.Connection = "Close";
# Implementing websocket support (www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
}
return (pipe);
}
sub vcl_pass {
# Called upon entering pass mode. In this mode, the request is passed on to the backend, and the
# backend's response is passed on to the client, but is not entered into the cache. Subsequent
# requests submitted over the same client connection are handled normally.
# return (pass);
}
*/
/*
sub vcl_hash {
# Called after vcl_recv to create a hash value for the request. This is used as a key
# to look up the object in Varnish.
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
# hash cookies for requests that have them
if (req.http.Cookie) {
hash_data(req.http.Cookie);
}
# Cache the HTTP vs HTTPs separately
if (req.http.X-Forwarded-Proto) {
hash_data(req.http.X-Forwarded-Proto);
}
# If the client supports compression, keep that in a different cache
if (req.http.Accept-Encoding) {
hash_data(req.http.Accept-Encoding);
}
return (lookup);
}
*/
sub vcl_hit {
# Called when a cache lookup is successful.
if (obj.ttl >= 0s) {
# A pure unadultered hit, deliver it
return (deliver);
}
# www.varnish-cache.org/docs/trunk/users-guide/vcl-grace.html
# When several clients are requesting the same page Varnish will send one request to the backend and place the others
# on hold while fetching one copy from the backend. In some products this is called request coalescing and Varnish does
# this automatically.
# If you are serving thousands of hits per second the queue of waiting requests can get huge. There are two potential
# problems - one is a thundering herd problem - suddenly releasing a thousand threads to serve content might send the
# load sky high. Secondly - nobody likes to wait. To deal with this we can instruct Varnish to keep the objects in cache
# beyond their TTL and to serve the waiting requests somewhat stale content.
#if (!std.healthy(req.backend_hint) && (obj.ttl + obj.grace > 0s)) {
#return (deliver);
#} else {
#return (miss);
#}
# We have no fresh fish. Lets look at the stale ones.
if (std.healthy(req.backend_hint)) {
# Backend is healthy. Limit age to 10s.
if (obj.ttl + 10s > 0s) {
#set req.http.grace = "normal(limited)";
return (deliver);
}
} else {
# backend is sick - use full grace
if (obj.ttl + obj.grace > 0s) {
#set req.http.grace = "full";
return (deliver);
}
}
}
sub vcl_miss {
# Called after a cache lookup if the requested document was not found in the cache. Its purpose
# is to decide whether or not to attempt to retrieve the document from the backend, and which
# backend to use.
return (fetch);
}
sub vcl_backend_response {
# Happens after we have read the response headers from the backend.
#
# Here you clean the response headers, removing silly Set-Cookie headers
# and other mistakes your backend does.
# If we host multiple domains on a server, here you can list the domains you DO NOT want to cache
# The first check matches both naked & "www" subdomains. Use the second for non generic subdomains.
#if (
# bereq.http.host ~ "(www\.)?(domain1.com|domain2.org|domain3.net)" ||
# bereq.http.host ~ "(subdomain.domain4.tld|othersubdomain.domain5.tld)"
#) {
# set beresp.uncacheable = true;
# return (deliver);
#}
# Pause ESI request and remove Surrogate-Control header
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
# I SET THE VARY TO ACCEPT-ENCODING, THIS OVERRIDES W3TC
# TENDANCY TO SET VARY USER-AGENT. YOU MAY OR MAY NOT WANT
# TO DO THIS
# ##########################################################
set beresp.http.Vary = "Accept-Encoding";
# Don't cache 50x responses
if (
beresp.status == 500 ||
beresp.status == 502 ||
beresp.status == 503 ||
beresp.status == 504
) {
return (abandon);
}
if (beresp.status == 301 || beresp.status == 302) {
# Sometimes, a 301 or 302 redirect formed via Apache's mod_rewrite can mess with the HTTP port that is being passed along.
# This often happens with simple rewrite rules in a scenario where Varnish runs on :80 and Apache on :8080 on the same box.
# A redirect can then often redirect the end-user to a URL on :8080, where it should be :80.
# This may need finetuning on your setup.
#
# To prevent accidental replace, we only filter the 301/302 redirects for now.
set beresp.http.Location = regsub(beresp.http.Location, ":[0-9]+", "");
# Don't cache redirects. Otherwise they're much more difficult to debug!
set beresp.ttl = 0s;
}
# === DO NOT CACHE ===
# Exclude the following paths (e.g. backend admins, user pages or ad URLs that require tracking)
# In Joomla specifically, you are advised to create specific entry points (URLs) for users to
# interact with the site (either common user logins or even commenting), e.g. make a menu item
# to point to a user login page (e.g. /login), including all related functionality such as
# password reset, email reminder and so on.
if(
bereq.url ~ "^/addons" ||
bereq.url ~ "^/administrator" ||
bereq.url ~ "^/cart" ||
bereq.url ~ "^/checkout" ||
bereq.url ~ "^/component/banners" ||
bereq.url ~ "^/component/socialconnect" ||
bereq.url ~ "^/component/users" ||
bereq.url ~ "^/connect" ||
bereq.url ~ "^/contact" ||
bereq.url ~ "^/login" ||
bereq.url ~ "^/logout" ||
bereq.url ~ "^/lost-password" ||
bereq.url ~ "^/my-account" ||
bereq.url ~ "^/register" ||
bereq.url ~ "^/signin" ||
bereq.url ~ "^/signup" ||
bereq.url ~ "^/wc-api" ||
bereq.url ~ "^/wp-admin/(?!admin-ajax\.php)" ||
bereq.url ~ "^/wp-login.php" ||
bereq.url ~ "^\?add-to-cart=" ||
bereq.url ~ "^\?wc-api=" ||
bereq.url ~ "/feed(/)?|wp-(comments-post|cron|activate|mail)\.php|register\.php" ||
bereq.url ~ "preview=true" ||
bereq.url ~ "/generate-catalog/" ||
bereq.url ~ "/\?s=" ||
bereq.url ~ "/shared-gc/includes/rest-api/" ||
bereq.url ~ "/m4rk" ||
bereq.url ~ "(\?|&)v=0"
) {
set beresp.uncacheable = true;
return (deliver);
}
# Don't cache HTTP authorization/authentication pages and pages with certain headers or cookies
if (
bereq.http.Authorization ||
bereq.http.Authenticate ||
bereq.http.X-Logged-In == "True" ||
bereq.http.Cookie ~ "userID" ||
bereq.http.Cookie ~ "joomla_[a-zA-Z0-9_]+" ||
bereq.http.Cookie ~ "(wordpress_(?!test_)[a-zA-Z0-9_]+|wp-postpass|resetpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+)"
) {
set beresp.uncacheable = true;
return (deliver);
}
# Don't cache ajax requests
if(beresp.http.X-Requested-With == "XMLHttpRequest" || bereq.url ~ "nocache") {
#set beresp.http.Cache-Control = "private, max-age=0, no-cache, no-store";
#set beresp.http.Expires = "Mon, 01 Jan 2001 00:00:00 GMT";
#set beresp.http.Pragma = "no-cache";
set beresp.uncacheable = true;
return (deliver);
}
# Don't cache backend response to posted requests
if (bereq.method == "POST") {
set beresp.uncacheable = true;
return (deliver);
}
# Ok, we're cool & ready to cache things
# so let's clean up some headers and cookies
# to maximize caching.
# Check for the custom "X-Logged-In" header to identify if the visitor is a guest,
# then unset any cookie (including session cookies) provided it's not a POST request.
if(beresp.http.X-Logged-In == "False" && bereq.method != "POST") {
unset beresp.http.Set-Cookie;
}
# Unset the "etag" header (suggested)
#unset beresp.http.etag;
# Unset the "pragma" header
unset beresp.http.Pragma;
# Unset the "vary" header (suggested)
unset beresp.http.Vary;
# Allow stale content, in case the backend goes down
set beresp.grace = 24h;
# This is how long Varnish will keep cached content
#set beresp.ttl = 60m;
# Modify "expires" header - www.varnish-cache.org/trac/wiki/VCLExampleSetExpires
#set beresp.http.Expires = "" + (now + beresp.ttl);
# If your backend server does not set the right caching headers for static assets,
# you can set them below (uncomment first and change 604800 - which 1 week - to whatever you
# want (in seconds)
#if (bereq.url ~ "\.(ico|jpg|jpeg|gif|png|bmp|webp|tiff|svg|svgz|pdf|mp3|flac|ogg|mid|midi|wav|mp4|webm|mkv|ogv|wmv|eot|otf|woff|ttf|rss|atom|zip|7z|tgz|gz|rar|bz2|tar|exe|doc|docx|xls|xlsx|ppt|pptx|rtf|odt|ods|odp)(\?[a-zA-Z0-9=]+)$") {
# set beresp.http.Cache-Control = "public, max-age=604800";
#}
# Enable cache for all static files
# The same argument as the static caches from above: monitor your cache size, if you get data nuked out of it, consider giving up the static file cache.
# Before you blindly enable this, have a read here: ma.ttias.be/stop-caching-static-files/
if (bereq.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
unset beresp.http.set-cookie;
set beresp.do_stream = true;
}
# We have content to cache, but it's got no-cache or other Cache-Control values sent
# So let's reset it to our main caching time (60m as used in this example configuration)
# The additional parameters specified (stale-while-revalidate & stale-if-error) are used
# by modern browsers to better control caching. Set these to twice & four times your main
# cache time respectively.
# This final setting will normalize cache-control headers for CMSs like Joomla
# which set max-age=0 even when the CMS' cache is enabled.
if (beresp.http.Cache-Control !~ "max-age" || beresp.http.Cache-Control ~ "max-age=0" || beresp.ttl < 60m) {
set beresp.http.Cache-Control = "public, max-age=3600, stale-while-revalidate=7200, stale-if-error=14400";
}
# Optionally set a larger TTL for pages with less than 180s of cache TTL
#if (beresp.ttl < 180s) {
# set beresp.http.Cache-Control = "public, max-age=180, stale-while-revalidate=360, stale-if-error=43200";
#}
return(deliver);
}
sub vcl_deliver {
# Happens when we have all the pieces we need, and are about to send the
# response to the client.
#
# You can do accounting or modifying the final object here.
/*
# Send a special header for excluded domains only
# The if statement can be identical to the ones in the vcl_recv() and vcl_fetch() functions above
if (
req.http.host ~ "(www\.)?(domain1.com|domain2.org|domain3.net)" ||
req.http.host ~ "(subdomain.domain4.tld|othersubdomain.domain5.tld)"
) {
set resp.http.X-Domain-Status = "EXCLUDED";
}
# Enforce redirect to HTTPS for specified domains only
if (
req.http.host ~ "(subdomain.domain4.tld|othersubdomain.domain5.tld)" &&
req.http.X-Forwarded-Proto !~ "(?i)https"
) {
set resp.http.Location = "https://" + req.http.host + req.url;
set resp.status = 302;
}
*/
# Send special headers that indicate the cache status of each web page
# Please note that obj.hits behaviour changed in 4.0, now it counts per objecthead, not per object
# and obj.hits may not be reset in some cases where bans are in use. See bug 1492 for details.
# So take hits with a grain of salt
if (obj.hits > 0) {
set resp.http.X-Cache = "V-HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "V-MISS";
}
# Remove some headers to improve security
unset resp.http.Server;
unset resp.http.X-Drupal-Cache;
unset resp.http.X-Varnish;
unset resp.http.Age;
unset resp.http.Via;
unset resp.http.Link;
unset resp.http.X-Generator;
#if (resp.http.magicmarker) {
# unset resp.http.magicmarker;
# set resp.http.age = "0";
#}
}
sub vcl_purge {
# Only handle actual PURGE HTTP methods, everything else is discarded
if (req.method != "PURGE") {
# restart request
set req.http.X-Purge = "Yes";
return(restart);
}
}
sub vcl_synth {
if (resp.status == 720) {
# We use this special error status 720 to force redirects with 301 (permanent) redirects
# To use this, call the following from anywhere in vcl_recv: return (synth(720, "http://host/new.html"));
set resp.http.Location = resp.reason;
set resp.status = 301;
return (deliver);
} elseif (resp.status == 721) {
# And we use error status 721 to force redirects with a 302 (temporary) redirect
# To use this, call the following from anywhere in vcl_recv: return (synth(720, "http://host/new.html"));
set resp.http.Location = resp.reason;
set resp.status = 302;
return (deliver);
}
return (deliver);
}
sub vcl_fini {
# Called when VCL is discarded only after all requests have exited the VCL.
# Typically used to clean up VMODs.
return (ok);
}