Last active
October 4, 2019 19:39
-
-
Save wuxmedia/c9fb997f3593fcde33aef68665722ea0 to your computer and use it in GitHub Desktop.
Generic-ish varnish 4 config, with wordpress, cms and other tweaks. courtesy of JT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Define which version of VCL we're running: | |
vcl 4; # Import used modules: | |
import std; | |
# Default backend definition. Set this to point to your content server. # Varnish can support basic loadbalancing & healthchecks | |
backend default { .host = "127.0.0.1"; | |
.port = "8080"; .connect_timeout = 60s; | |
.first_byte_timeout = 90s; .between_bytes_timeout = 60s; | |
# Define basic healthchecking-probe. # Backend must respond to 3 http GETs within the last 5 for GET /licence.txt with 200 OK to be considered 'healthy' | |
# The URL we're probing for assumes a WordPress install is available on the backend. # .probe = { | |
# .url = "/license.txt"; # .timeout = 3s; | |
# .interval = 5s; # .window = 5; | |
# .threshold = 3; # } | |
# Set this if you want to throttle connections to the backend webserver #.max_connections = 40; | |
} | |
# List of backend webservers allowed to send PURGE requests on behalf of the site. | |
# For a modern cluster, you should put the private LAN prod range here, e.g. # "10.10.10.0/24"; | |
acl purgeallow { "127.0.0.1"; | |
} | |
sub vcl_recv { # 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. | |
################# | |
# Required code | |
# | |
# The following code is required on every request. It normalizes some data, prepares backend signal, and does some | |
# very simple defensive action. Don't add *anything* here for normal alterations | |
# | |
######### | |
# Block simple slowloris attack | |
if (req.url == "400") { return (synth(403, "Permission Denied.")); } | |
# Block various overt SQLi attacks | |
if ((req.url ~ "(?i).+SELECT.+FROM" && !req.url ~ "\?cs-from\=") || | |
req.url ~ "(?i).+UNION\s+SELECT" || | |
req.url ~ "UNION.*SELECT" || | |
req.url ~ "(?i).+INSERT.+INTO" || | |
req.url ~ "(?i).+DELETE.+FROM" || | |
req.url ~ "(?i).+DROP.+TABLE" || | |
req.url ~ "(?i).+DROP.+DATABASE" ) { | |
return(synth(404, "File not found.")); | |
} | |
# Block overt php remote code execution via URL. | |
if (req.url ~ "eval\(" ) { | |
#req.url ~ "(<|\%3C)?\?(php)?.*(php)?\?(>|\%3E)?" || # Was causing problems | |
return(synth(404, "File not found.")); | |
} | |
# Ensure HTTPS header signal is set. X-HTTPS is used as a signal to hash function, to differentiate | |
# objects cached by visitors served via HTTPS | |
if (req.http.X-Forwarded-Proto == "https") { set req.http.X-HTTPS = "on"; } | |
if (!req.http.X-HTTPS) { set req.http.X-HTTPS = "off"; } | |
# Perfom basic device detection (used to differentiate cache based on mobile/pc/smartphone. Calls predefined function | |
call identify_device; | |
# Handle incoming PURGE requests. Intergrates with WP plugin "Varnish HTTP Purge", or any PURGE-sending plugin | |
if (req.method == "PURGE" && client.ip ~purgeallow ) { | |
if (req.http.X-Purge-Method) { | |
if (req.http.X-Purge-Method ~ "(?i)regex") { call purge_regex; } | |
elseif (req.http.X-Purge-Method ~ "(?i)exact") { call purge_exact; } | |
else { call purge_page; } | |
} | |
return(synth(200, "Ban added")); | |
} | |
# You can also trigger this by sending a manual CURL command against the active varnish server; | |
# curl -X PURGE -H 'Host: example.com' -H 'X-Purge-Method: regex' http://10.10.10.1:6081/\.\* | |
# WARNING: In a multi-varnish environment where 2 varnish servers are hot, you'll need to fire the curl twice, | |
# once for each cache server. | |
# Detect/provide end-users' ip. If end-user routed through CloudFlare, use CloudFlare's header | |
if ( req.http.CF-Connecting-IP ) { | |
set req.http.X-Forwarded-For = req.http.CF-Connecting-IP; | |
set req.http.X-Real-IP = req.http.CF-Connecting-IP; | |
} | |
# Otherwise, use the IP address previously set by Nginx/CloudFront | |
elseif ( req.http.X-Forwarded-Proto ) { | |
set req.http.X-Forwarded-For = req.http.X-Real-IP; | |
} | |
# We're getting connected to directly. | |
else { | |
set req.http.X-Forwarded-For = client.ip; | |
set req.http.X-Real-IP = client.ip; | |
} | |
set req.http.grace = "none"; | |
# Normalize Accept-Encoding to increase cache hitrate | |
if (req.http.Accept-Encoding) { | |
if (req.http.Accept-Encoding ~ "gzip") { set req.http.Accept-Encoding = "gzip"; } | |
elsif (req.http.Accept-Encoding ~"deflate") { set req.http.Accept-Encoding = "deflate"; } | |
else { unset req.http.Accept-Encoding; } | |
} | |
# Stop Vary header from indicating anything but Accept-Encoding; no user-agent for example | |
# Some caching plugins/guides errantly recommend adding User-Agent to accept-encoding. This breaks varnish. | |
if (req.http.Vary) { | |
if (req.http.Vary ~ "Accept-Encoding") { set req.http.Vary = "Accept-Encoding"; } | |
else { unset req.http.Vary; } | |
} | |
################### | |
# | |
# End Required Code. Put overrides below here | |
# | |
################################# | |
# Don't cache ajax request | |
if (req.url ~ "admin-ajax.php") { return (pass); } | |
# dont cache work. | |
if (req.url ~ "work/htdocs") { return (pass); } | |
# Basic blocking of some common malicious requests | |
if (req.url ~ "(\.\..+)+" || req.url ~ "\.\.%2F.+$" || req.url ~ "self%2Fenviron" ) { | |
set req.url = regsub(req.url, "\?.*$", ""); | |
} | |
# Don't cache dynamic content or AJAX | |
if (req.method == "POST") { return (pass); } | |
if (req.url ~ "(xmlrpc.php|wlmanifest.xml)") { return(pass); } | |
# If the user is logged in, don't cache | |
if (req.http.Cookie ~ "wordpress_logged_in") { return(pass); } | |
# If the user is accessing wp-admin, don't cache | |
if (req.url ~ "wp-admin") { return(pass); } | |
# Don't cache ajax requests, logins, registrations, or comment posts | |
if(req.http.X-Requested-With == "XMLHttpRequest" || req.url ~ "(wp-comments-post.php|wp-login.php|register.php)") { | |
return (pass); | |
} | |
# Forcibly cache statically-uploaded/stored media | |
if (req.url ~ "/wp-content/uploads/") { unset req.http.cookie; return (hash); } | |
# Force all image/javascript/static-media to come from cache | |
# This won't affect theme-backend.css.php, as it ends in .php, not .css | |
if (req.url ~ "\.(css|js|ico|png|gif|svg|jpg|swf|jpeg|zip)$" || req.url ~ ".*(minify).*\.(css|js).*") { | |
unset req.http.cookie; | |
return (hash); | |
} | |
# Cache requests for various versioned js/css files | |
if (req.url ~ "\.js?ver") { unset req.http.cookie; return (hash); } | |
if (req.url ~ "\.css?ver") { unset req.http.cookie; return (hash); } | |
# If you've commented, don't cache pages, but do cache static media (above) | |
if (req.http.Cookie ~ "comment_author_") { return(pass); } | |
# Remove Google Analytics and Piwik cookies everywhere | |
if (req.http.Cookie) { | |
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js)=[^;]*", ""); | |
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_pk_(ses|id)[\.a-z0-9]*)=[^;]*", ""); | |
} | |
# Remove the cookie when it's empty | |
if (req.http.Cookie == "") { | |
unset req.http.Cookie; | |
} | |
# Alter the variables in this stanza to delete *all* cookies other than the named cookies, 'cookie1,cookie2'. | |
#if (req.http.Cookie) { | |
# set req.http.Cookie = ";" + req.http.Cookie; | |
# set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); | |
# set req.http.Cookie = regsuball(req.http.Cookie, ";(cookie1|cookie2)=", "; \1="); | |
# set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); | |
# set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); | |
# if (req.http.Cookie == "") { unset req.http.Cookie; } | |
#} | |
# To cache incredibly aggressively, delete all remaining cookies; | |
# unset req.http.Cookie; | |
# At this point don't cache if they have a cookie. | |
if (req.http.Cookie) { return(pass); } | |
# At this point, attempt to return anything remaining from cache | |
return(hash); | |
} | |
sub vcl_backend_response { | |
# Happens after we have read the response headers from the backend. | |
# | |
# Here you clean the response headers | |
# Provide settings for use by ban lurker/bans | |
set beresp.http.x-url = bereq.url; | |
set beresp.http.x-host = bereq.http.host; | |
# Set grace; provides for protection against stampeeding horde problem | |
if (beresp.ttl > 0s) { | |
set beresp.grace = 1h; | |
} | |
# If you're in the process of logging in, activating a basic PHP session, *don't* cache | |
if (beresp.http.Set-Cookie ~ "(wordpress_|PHPSESS)") { | |
set beresp.uncacheable = true; | |
return (deliver); | |
} | |
# If they're reporting login issues, or issues with redirect, uncomment the following to stop caching redirects | |
#if (beresp.status == 302 || beresp.status == 301) { | |
# set beresp.uncacheable = true; | |
# return (deliver); | |
#} | |
# Mostly dynamic areas. admin-ajax *can* be used in a form that is cacheable, but no-one does that without us telling them to | |
if (bereq.url ~ "(wp-admin|wp-login.php|admin-ajax.php|wp-cron.php)" || | |
bereq.http.cookie ~ "logged_in" || | |
beresp.status == 403 ) { | |
set beresp.ttl = 120s; | |
set beresp.uncacheable = true; | |
return (deliver); | |
} | |
# Force redirects to be cached | |
#if (beresp.status == 301 || beresp.status == 302) { | |
# unset beresp.http.Set-Cookie; | |
# unset beresp.http.Cookie; | |
# set beresp.ttl = 2m; | |
# return(deliver); | |
#} | |
#wlmanifest.xml is used for windows live writer to write to WordPress. Remove it if they're unlikely to use Windows Live Writer. | |
# Don't cache ajax requests or fully dynamic tools | |
if ( bereq.http.X-Requested-With == "XMLHttpRequest" || | |
bereq.url ~ "(wp-comments-post.php|wp-login.php|register.php|xmlrpc.php|wlmanifest.xml)") { | |
# set beresp.ttl = 120s; | |
set beresp.uncacheable = true; | |
return (deliver); | |
} | |
# Item requested with POST, uncacheable response | |
if (bereq.method == "POST") { | |
set beresp.uncacheable = true; | |
return (deliver); | |
} | |
#################### | |
# Cache TTLs | |
# WARNING: We set the browser to keep static media items for 7 days, the Google recommended default. This will be too long | |
# For development or staging work, make sure to bypass cache for those. | |
# Override cache times for statically-uploaded media | |
if (bereq.url ~ "/wp-content/uploads/") { | |
unset beresp.http.Set-Cookie; # dis-allow cookie setting for static media | |
unset beresp.http.expires; | |
unset beresp.http.pragma; | |
# Set how long the client should keep the item by default | |
set beresp.http.cache-control = "max-age=604800"; | |
set beresp.ttl = 4h; | |
return (deliver); | |
} | |
# Set cache time for static media. Will not catch theme-backend.css.php. | |
if ((bereq.method == "GET" && bereq.url ~ "\.(css|js|xml|gif|jpg|jpeg|swf|svg|png|zip|ico|img|wmf|txt|flv)$") || | |
bereq.url ~ "\.(minify).*.(css|js).*" || | |
bereq.url ~ "\.(css|js|svg)\?ver") { | |
unset beresp.http.Set-Cookie; # dis-allow cookie setting | |
unset beresp.http.expires; | |
unset beresp.http.pragma; | |
# Set how long the client should keep the item by default | |
set beresp.http.cache-control = "max-age=604800"; | |
set beresp.ttl = 4h; | |
return (deliver); | |
} | |
# If it's a not found, or a server error, cache it for 1 minute | |
if (beresp.status == 404 || beresp.status >= 500) { set beresp.ttl = 1m; } | |
# If you're logged in to wordpress, don't cache. | |
if (bereq.http.Cookie ~ "wordpress_") { | |
set beresp.ttl = 120s; | |
set beresp.uncacheable = true; | |
return (deliver); | |
} | |
# if you're still here, override to default minimum ttl for object storage | |
if (beresp.ttl < 30s) { | |
set beresp.ttl=900s; | |
} | |
# Reset any remaining outbound HTTP headers | |
unset beresp.http.expires; | |
unset beresp.http.pragma; | |
# Set how long the client should keep the item by default | |
set beresp.http.cache-control = "max-age=600"; | |
# Override browsers to keep styling and dynamics to Google recommended 1 week | |
if (bereq.url ~ ".minify.*\.(css|js).*") { set beresp.http.cache-control = "max-age=604800"; } | |
if (bereq.url ~ ".*(css|js)$") { set beresp.http.cache-control = "max-age=604800"; } | |
# Override browsers to keep static media for 1 week | |
if (bereq.url ~ ".(xml|gif|jpg|jpeg|swf|png|zip|ico|img|wmf|txt)$") { | |
set beresp.http.cache-control = "max-age=604800"; | |
} | |
# Block setting a cookie we've not handled previously. | |
unset beresp.http.Set-Cookie; | |
# Deliver data to visitor | |
return(deliver); | |
} | |
# HTTPS Handling | |
sub vcl_hash { | |
# HTTPs differentiation | |
if ( req.http.X-HTTPS == "on" ) { hash_data("ssl"); } | |
hash_data(req.url); | |
if (req.http.host) { hash_data(req.http.host); } | |
else { hash_data(server.ip); } | |
# Device detection | |
hash_data(req.http.X-Device); | |
return (lookup); | |
} | |
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. | |
# Define grace | |
set resp.http.grace = req.http.grace; | |
# Set hit indicator headers, for cache tuning | |
if (obj.hits > 0) { | |
set resp.http.X-Cache = "HIT"; | |
} else { | |
set resp.http.X-Cache = "MISS"; | |
} | |
# Remove the temp headers used for ban-luker friendly bans | |
unset resp.http.x-url; | |
unset resp.http.x-host; | |
# Optionally enforce HSTS | |
#set resp.http.Strict-Transport-Security = "max-age=31536000; includeSubDomains"; | |
} | |
sub vcl_hit { | |
if (obj.ttl >= 0s) { | |
# normal hit | |
return (deliver); | |
} | |
# We have no fresh objects. Check stale ones. | |
if (std.healthy(req.backend_hint)) { | |
# Backend is healthy. Limit age to 10s. | |
if (obj.ttl + 10s > 0s) { | |
return (deliver); | |
} else { | |
# No candidate for grace. Fetch a fresh object. | |
return(miss); | |
} | |
} else { | |
# backend is sick - use full grace | |
if (obj.ttl + obj.grace > 0s) { | |
return (deliver); | |
} else { | |
# no graced object. | |
return (miss); | |
} | |
} | |
} | |
sub vcl_backend_error { | |
# If we're about to return an error, and we've not retried three times, retry | |
if (bereq.retries < 4) { | |
return (retry); | |
} | |
} | |
# Routine to identify and classify a device based on User-Agent | |
sub identify_device { | |
# Default to classification as a PC | |
set req.http.X-Device = "pc"; | |
if (req.http.User-Agent ~ "iPad" ) { | |
# The User-Agent indicates it's a iPad - so classify as a tablet | |
set req.http.X-Device = "mobile-tablet"; | |
} | |
elsif (req.http.User-Agent ~ "iP(hone|od)" || req.http.User-Agent ~ "Android" ) { | |
# The User-Agent indicates it's a iPhone, iPod or Android - so let's classify as a touch/smart phone | |
set req.http.X-Device = "mobile-smart"; | |
} | |
elsif (req.http.User-Agent ~ "SymbianOS" || req.http.User-Agent ~ "^BlackBerry" || req.http.User-Agent ~ "^SonyEricsson" || req.http.User-Agent ~ "^Nokia" || req.http.User-Agent ~ "^SAMSUNG" || req.http.User-Agent ~ "^LG") { | |
# The User-Agent indicates that it is some other mobile devices, so let's classify it as such. | |
set req.http.X-Device = "mobile-other"; | |
} | |
} | |
# Define ways of purging based on regex. | |
# Treat the request URL as a regular expression. | |
sub purge_regex { | |
ban("obj.http.x-url ~ " + req.url + " && obj.http.x-host == " + regsub(req.http.host, "(.*):8080", "\1")); | |
#ban("req.url ~ " + req.url + " && req.http.host == " + regsub(req.http.host, "(.*):81", "\1")); | |
} | |
# Exact purging | |
# Use the exact request URL (including any query params) | |
sub purge_exact { | |
ban("obj.http.x-url == " + req.url + " && obj.http.x-host == " + regsub(req.http.host, "(.*):8080", "\1")); | |
#ban("req.url == " + req.url + " && req.http.host == " + regsub(req.http.host, "(.*):81", "\1")); | |
} | |
# Page purging (default) | |
# Use the exact request URL, but ignore any query params | |
sub purge_page { | |
set req.url = regsub(req.url, "\?.*$", ""); | |
ban("obj.http.x-url == " + req.url + " && obj.http.x-host == " + regsub(req.http.host, "(.*):8080", "\1")); | |
#ban("req.url == " + req.url + " && req.http.host == " + regsub(req.http.host, "(.*):81", "\1")); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment