forked from Ipstenu/varnish-http-purge
-
-
Notifications
You must be signed in to change notification settings - Fork 5
default.vcl
Danila Vershinin edited this page Jun 4, 2025
·
3 revisions
# WordPress Varnish VCL
# VCL compiler declaration
vcl 4.1;
# Imports
import std;
# Includes (DO NOT CHANGE ORDER!)
include "lib/cloudflare.vcl";
include "lib/xforward.vcl";
include "lib/purge.vcl";
include "lib/bigfiles_pipe.vcl";
include "lib/static.vcl";
# Default backend definition. Set this to point to your content server.
backend default {
.host = "MACHINE.IP.ADDRESS";
.port = "PORT";
}
acl purge {
"localhost";
"127.0.0.1";
"0.0.0.0";
"::1";
"IP OF MACHINE";
}
# We just received the request from the site visitor.
sub vcl_recv {
# LetsEncrypt Certbot passthrough
if (req.url ~ "^/\.well-known/acme-challenge/") {
return (pass);
}
# httproxy
unset req.http.proxy;
# Force HTTPS because of Hitch
if ((std.port(server.ip) == 443)) {
set req.http.X-Forwarded-Proto = "https";
}
# Normalize the query arguments (but exclude for WordPress' backend)
if (req.url !~ "wp-admin|wp-login") {
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);
}
# === URL manipulation ===
# Remove the Google Analytics added parameters
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, "\?$", "");
}
# Remove variable for replying to comments, so people still get cache
if (req.url ~ "\?replytocom") {
set req.url = regsub(req.url, "\?replytocom=.*$", "");
}
# 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, "\?$", "");
}
# === DO NOT CACHE ===
# never cache anything except GET/HEAD
if (req.method != "GET" && req.method != "HEAD") {
return(pass);
}
# Don't cache HTTP authorization/authentication pages and pages with certain headers or cookies
if (
req.http.Authorization ||
req.http.Authenticate ||
req.http.X-Logged-In == "True" ||
req.http.Cookie ~ "wordpress_(?!test_)[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]+|wordpress_logged_in_|comment_author|PHPSESSID"
) {
return(pass);
}
# Exclude the following paths (e.g. backend admins, user pages or ad URLs that require tracking)
if (
req.url ~ "add_to_cart" ||
req.url ~ "edd_action" ||
req.url ~ "nocache" ||
req.url ~ "^/addons" ||
req.url ~ "^/bb-admin" ||
req.url ~ "^/bb-login.php" ||
req.url ~ "^/bb-reset-password.php" ||
req.url ~ "^/cart" ||
req.url ~ "^/checkout" ||
req.url ~ "^/control.php" ||
req.url ~ "^/login" ||
req.url ~ "^/logout" ||
req.url ~ "^/lost-password" ||
req.url ~ "^/my-account" ||
req.url ~ "^/product" ||
req.url ~ "^/register" ||
req.url ~ "^/register.php" ||
req.url ~ "^/server-status" ||
req.url ~ "^/signin" ||
req.url ~ "^/signup" ||
req.url ~ "^/stats" ||
req.url ~ "^/wc-api" ||
req.url ~ "^/wp-admin" ||
req.url ~ "^/wp-comments-post.php" ||
req.url ~ "^/wp-cron.php" ||
req.url ~ "^/wp-login.php" ||
req.url ~ "^/wp-activate.php" ||
req.url ~ "^/wp-mail.php" ||
req.url ~ "^/wp-login.php" ||
req.url ~ "^\?add-to-cart=" ||
req.url ~ "^\?wc-api=" ||
req.url ~ "^/preview="
) {
return(pass);
}
# never cache ajax requests
if (req.http.X-Requested-With == "XMLHttpRequest") {
return(pass);
}
# === Generic cookie manipulation ===
# Remove the "has_js" cookie
set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
# Strip cookies with two underscores first (Cloudflare etc)
set req.http.Cookie = regsuball(req.http.Cookie, "__[a-z]=[^;]+(; )?", "");
# 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 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 cookies from common plugins
# Add This
set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", "");
# WooCommerce
set req.http.Cookie = regsuball(req.http.Cookie, "wooTracker=[^;]+(; )?", "");
# EDD
set req.http.Cookie = regsuball(req.http.Cookie, "edd=[^;]+(; )?", "");
# WordFence
set req.http.Cookie = regsuball(req.http.Cookie, "wfvt_[0-9]+=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "wordfence_verifiedHuman=[^;]+(; )?", "");
# Remove the Quant Capital cookies (added by some plugin, all __qca)
set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");
# Are there cookies left with only spaces or that are empty?
if (req.http.cookie ~ "^\s*$") {
unset req.http.cookie;
}
# 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;
}
# === 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;
}
}
### looks like we might actually cache it!
# fix up the request
set req.grace = 2m;
return(hash);
}
sub vcl_hash {
# Provide support for SSL termination
if (req.http.X-Forwarded-Proto ~ "https") {
hash_data(req.http.X-Forwarded-Proto);
}
# Add the browser cookie only when a WordPress cookie found.
if (req.http.Cookie ~ "wp-postpass_|wordpress_logged_in_|comment_author|PHPSESSID") {
hash_data(req.http.Cookie);
}
}
sub vcl_backend_response {
# Don't cache 50x responses
if (beresp.status >= 500) {
return (abandon);
}
# === DO NOT CACHE ===
# Exclude the following paths (e.g. backend admins, user pages or ad URLs that require tracking)
if(
bereq.url ~ "add_to_cart" ||
bereq.url ~ "edd_action" ||
bereq.url ~ "nocache" ||
bereq.url ~ "^/addons" ||
bereq.url ~ "^/bb-admin" ||
bereq.url ~ "^/bb-login.php" ||
bereq.url ~ "^/bb-reset-password.php" ||
bereq.url ~ "^/cart" ||
bereq.url ~ "^/checkout" ||
bereq.url ~ "^/control.php" ||
bereq.url ~ "^/login" ||
bereq.url ~ "^/logout" ||
bereq.url ~ "^/lost-password" ||
bereq.url ~ "^/my-account" ||
bereq.url ~ "^/product" ||
bereq.url ~ "^/register" ||
bereq.url ~ "^/register.php" ||
bereq.url ~ "^/server-status" ||
bereq.url ~ "^/signin" ||
bereq.url ~ "^/signup" ||
bereq.url ~ "^/stats" ||
bereq.url ~ "^/wc-api" ||
bereq.url ~ "^/wp-admin" ||
bereq.url ~ "^/wp-comments-post.php" ||
bereq.url ~ "^/wp-cron.php" ||
bereq.url ~ "^/wp-login.php" ||
bereq.url ~ "^/wp-activate.php" ||
bereq.url ~ "^/wp-mail.php" ||
bereq.url ~ "^/wp-login.php" ||
bereq.url ~ "^\?add-to-cart=" ||
bereq.url ~ "^\?wc-api=" ||
bereq.url ~ "^/preview="
) {
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 ~ "wordpress_(?!test_)[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]+|wordpress_logged_in_|comment_author|PHPSESSID"
) {
set beresp.http.X-Cacheable = "NO:Logged in/Got Sessions";
set beresp.uncacheable = true;
return (deliver);
}
# Don't cache ajax requests
if(beresp.http.X-Requested-With == "XMLHttpRequest" || bereq.url ~ "nocache") {
set beresp.http.X-Cacheable = "NO:Ajax";
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;
}
# If WordFence cookies are found, remove them. They show up here because they get set on the
# backend of the server.
if (beresp.http.Set-Cookie && beresp.http.Set-Cookie ~ "wfvt_|wordfence_verifiedHuman") {
unset beresp.http.Set-Cookie;
}
# Catch obvious reasons we cannot cache (i.e. cookies exist)
if (beresp.http.Set-Cookie) {
set beresp.http.X-Cacheable = "NO:Got Cookies";
set beresp.uncacheable = true;
return (deliver);
}
# Varnish determined the object was not cacheable
if (beresp.ttl <= 0s && beresp.status != 404) {
set beresp.http.X-Cacheable = "NO:Not Cacheable";
set beresp.uncacheable = true;
return (deliver);
# You are respecting the Cache-Control=private header from the backend
} else if (beresp.http.Cache-Control ~ "private") {
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
set beresp.uncacheable = true;
return (deliver);
# You are extending the lifetime of the object artificially
} else if (beresp.ttl < 60m) {
set beresp.ttl = 60m;
set beresp.grace = 60m;
set beresp.http.X-Cacheable = "YES:Forced";
# Varnish determined the object was cacheable
} else {
set beresp.http.X-Cacheable = "YES";
}
# Unset the "pragma" header (suggested)
unset beresp.http.Pragma;
# Unset the "vary" header (suggested)
unset beresp.http.Vary;
# Unset the "etag" header (optional)
#unset beresp.http.etag;
# Allow stale content, in case the backend goes down
set beresp.grace = 24h;
# Enforce your own cache TTL (optional)
set beresp.ttl = 60m;
# Modify "expires" header - https://www.varnish-cache.org/trac/wiki/VCLExampleSetExpires (optional)
if (beresp.http.Expires == "") {
set beresp.http.Expires = "" + (now + beresp.ttl);
}
## Add support for gzip/brotli
if (beresp.http.content-type ~ "text") {
set beresp.do_gzip = true;
}
# We have content to cache, but it's got no-cache or other Cache-Control values sent
# 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 that 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") {
set beresp.http.Cache-Control = "public, max-age=3600, stale-while-revalidate=360, stale-if-error=43200";
}
# Optionally set a larger TTL for pages with less than the timeout of cache TTL
if (beresp.ttl < 3600s) {
set beresp.http.Cache-Control = "public, max-age=3600, stale-while-revalidate=360, stale-if-error=43200";
}
return (deliver);
}
sub vcl_deliver {
# Send a special header for excluded domains only
if (
req.http.host ~ ".stage.site" ||
req.http.host ~ ".dream.website"
) {
set resp.http.X-Domain-Status = "EXCLUDED";
}
# Send special headers that indicate the cache status of each web page
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";
# Force revalidate for browser if the cache is missed.
set resp.http.Cache-Control = "must-revalidate, max-age=0";
}
# Remove headers we do not use
unset resp.http.X-Cache-Hits;
# Add powered by
set resp.http.X-Powered-By = "DreamPress";
return (deliver);
}