-
-
Save matthewjackowski/062be03b41a68edbadfc to your computer and use it in GitHub Desktop.
# A heavily customized VCL to support WordPress | |
# Some items of note: | |
# Supports https | |
# Supports admin cookies for wp-admin | |
# Caches everything | |
# Support for custom error html page | |
vcl 4.0; | |
import directors; | |
import std; | |
# Assumed 'wordpress' host, this can be docker servicename | |
backend default { | |
.host = "wordpress"; | |
.port = "80"; | |
} | |
acl purge { | |
"localhost"; | |
"127.0.0.1"; | |
} | |
sub vcl_recv { | |
# Only a single backend | |
set req.backend_hint= default; | |
# Setting http headers for backend | |
set req.http.X-Forwarded-For = client.ip; | |
set req.http.X-Forwarded-Proto = "https"; | |
# Unset headers that might cause us to cache duplicate infos | |
unset req.http.Accept-Language; | |
unset req.http.User-Agent; | |
# The purge...no idea if this works | |
if (req.method == "PURGE") { | |
if (!client.ip ~ purge) { | |
return(synth(405,"Not allowed.")); | |
} | |
ban("req.url ~ /"); | |
return (purge); | |
} | |
if ( std.port(server.ip) == 6080) { | |
set req.http.x-redir = "https://" + req.http.host + req.url; | |
return (synth(750, "Moved permanently")); | |
} | |
# drop cookies and params from static assets | |
if (req.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") { | |
unset req.http.cookie; | |
set req.url = regsub(req.url, "\?.*$", ""); | |
} | |
# drop tracking params | |
if (req.url ~ "\?(utm_(campaign|medium|source|term)|adParams|client|cx|eid|fbid|feed|ref(id|src)?|v(er|iew))=") { | |
set req.url = regsub(req.url, "\?.*$", ""); | |
} | |
# pass wp-admin urls | |
if (req.url ~ "(wp-login|wp-admin)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php") { | |
return (pass); | |
} | |
# pass wp-admin cookies | |
if (req.http.cookie) { | |
if (req.http.cookie ~ "(wordpress_|wp-settings-)") { | |
return(pass); | |
} else { | |
unset req.http.cookie; | |
} | |
} | |
} | |
sub vcl_backend_response { | |
# retry a few times if backend is down | |
if (beresp.status == 503 && bereq.retries < 3 ) { | |
return(retry); | |
} | |
if (bereq.http.Cookie ~ "(UserID|_session)") { | |
# if we get a session cookie...caching is a no-go | |
set beresp.http.X-Cacheable = "NO:Got Session"; | |
set beresp.uncacheable = true; | |
return (deliver); | |
} elsif (beresp.ttl <= 0s) { | |
# Varnish determined the object was not cacheable | |
set beresp.http.X-Cacheable = "NO:Not Cacheable"; | |
} elsif (beresp.http.set-cookie) { | |
# You don't wish to cache content for logged in users | |
set beresp.http.X-Cacheable = "NO:Set-Cookie"; | |
set beresp.uncacheable = true; | |
return (deliver); | |
} elsif (beresp.http.Cache-Control ~ "private") { | |
# You are respecting the Cache-Control=private header from the backend | |
set beresp.http.X-Cacheable = "NO:Cache-Control=private"; | |
set beresp.uncacheable = true; | |
return (deliver); | |
} else { | |
# Varnish determined the object was cacheable | |
set beresp.http.X-Cacheable = "YES"; | |
# Remove Expires from backend, it's not long enough | |
unset beresp.http.expires; | |
# Set the clients TTL on this object | |
set beresp.http.cache-control = "max-age=900"; | |
# Set how long Varnish will keep it | |
set beresp.ttl = 1w; | |
# marker for vcl_deliver to reset Age: | |
set beresp.http.magicmarker = "1"; | |
} | |
# unset cookies from backendresponse | |
if (!(bereq.url ~ "(wp-login|wp-admin)")) { | |
set beresp.http.X-UnsetCookies = "TRUE"; | |
unset beresp.http.set-cookie; | |
set beresp.ttl = 1h; | |
} | |
# long ttl for assets | |
if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") { | |
set beresp.ttl = 365d; | |
} | |
set beresp.grace = 1w; | |
} | |
sub vcl_hash { | |
if ( req.http.X-Forwarded-Proto ) { | |
hash_data( req.http.X-Forwarded-Proto ); | |
} | |
} | |
sub vcl_backend_error { | |
# display custom error page if backend down | |
if (beresp.status == 503 && bereq.retries == 3) { | |
synthetic(std.fileread("/etc/varnish/error503.html")); | |
return(deliver); | |
} | |
} | |
sub vcl_synth { | |
# redirect for http | |
if (resp.status == 750) { | |
set resp.status = 301; | |
set resp.http.Location = req.http.x-redir; | |
return(deliver); | |
} | |
# display custom error page if backend down | |
if (resp.status == 503) { | |
synthetic(std.fileread("/etc/varnish/error503.html")); | |
return(deliver); | |
} | |
} | |
sub vcl_deliver { | |
# oh noes backend is down | |
if (resp.status == 503) { | |
return(restart); | |
} | |
if (resp.http.magicmarker) { | |
# Remove the magic marker | |
unset resp.http.magicmarker; | |
# By definition we have a fresh object | |
set resp.http.age = "0"; | |
} | |
if (obj.hits > 0) { | |
set resp.http.X-Cache = "HIT"; | |
} else { | |
set resp.http.X-Cache = "MISS"; | |
} | |
set resp.http.Access-Control-Allow-Origin = "*"; | |
} | |
sub vcl_hit { | |
if (req.method == "PURGE") { | |
return(synth(200,"OK")); | |
} | |
} | |
sub vcl_miss { | |
if (req.method == "PURGE") { | |
return(synth(404,"Not cached")); | |
} | |
} |
I've just debugged this for a site I was working on:
please replace the purge method there:
if (req.method == "PURGE") { if (!client.ip ~ purge) { return(synth(405,"Not allowed.")); } return (purge); }
with
if (req.method == "PURGE") { if (!client.ip ~ purge) { return(synth(405,"Not allowed.")); } ban("req.url ~ /"); return (purge); }
The purge is done with
ban("req.url ~ /");
otherwise the purge isn't actually conducted
Thanks for this, this worked for me too
Hi everyone! OP here, I had no idea so many people were using this as I have moved away from using Varnish for my Wordpress sites in favor of static publishing but am happy to maintain this config.
@tschirmer - Thanks for the update, I'll make that addition!
Hey there,
could you point me in the right direction in order to make the WP backend work? I'm getting a redirect loop error.
Just to be clear, my original intent is to NOT to have the Wordpress BE work through Varnish. There is just so much to think about/can go wrong. Additionally I have security concerns around this because many Wordpress attacks target BE urls.
Instead I would recommend having a completely separate route setup for site admins, operationally it helps so much to have separated activity logs, IP security, etc.
Watch out for this booby trap!
set resp.http.Access-Control-Allow-Origin = "*";
Certainly there is no intent for this to be a "booby trap". The intent is to cache external assets that CORS would otherwise block. Unfortunately I'm not sure how to make this less "scary". Maybe put a url condition on it like this: if (req.url ~ "/fonts/")
I'm open to any suggestions/feedback here.
Why are u removing the User-Agent header from the request?
Maybe i'm missing something but i don't get how that info could be duplicated and in addition to that removing the UA is a loss of control on many security things you can do based on the application that made the request, or at least the most of these.
Great comment! This is definitely a rather "opinionated" setting.
I completely agree with your analysis, but my thinking goes along this line:
The goal is to let Varnish quickly give out the cached version to whoever asks for it, I shouldn't really care if it's a browser, a bot whatever (its so simple to impersonate browsers that I'm not sure how valuable it is from a security point of view)
Additionally I really really don't want Varnish keeping separate cached copies for every different User-Agent. I did try to get it to ignore it instead of dropping it altogether, but I couldn't make it work reliably (I kept seeing dups in the cache file).
Why are u removing the User-Agent header from the request?
Maybe i'm missing something but i don't get how that info could be duplicated and in addition to that removing the UA is a loss of control on many security things you can do based on the application that made the request, or at least the most of these.Great comment! This is definitely a rather "opinionated" setting.
I completely agree with your analysis, but my thinking goes along this line:
The goal is to let Varnish quickly give out the cached version to whoever asks for it, I shouldn't really care if it's a browser, a bot whatever (its so simple to impersonate browsers that I'm not sure how valuable it is from a security point of view)
Additionally I really really don't want Varnish keeping separate cached copies for every different User-Agent. I did try to get it to ignore it instead of dropping it altogether, but I couldn't make it work reliably (I kept seeing dups in the cache file).
For me this line stops WP's visual editor loading
Why are u removing the User-Agent header from the request?
Maybe i'm missing something but i don't get how that info could be duplicated and in addition to that removing the UA is a loss of control on many security things you can do based on the application that made the request, or at least the most of these.Great comment! This is definitely a rather "opinionated" setting.
I completely agree with your analysis, but my thinking goes along this line:
The goal is to let Varnish quickly give out the cached version to whoever asks for it, I shouldn't really care if it's a browser, a bot whatever (its so simple to impersonate browsers that I'm not sure how valuable it is from a security point of view)
Additionally I really really don't want Varnish keeping separate cached copies for every different User-Agent. I did try to get it to ignore it instead of dropping it altogether, but I couldn't make it work reliably (I kept seeing dups in the cache file).For me this line stops WP's visual editor loading
This should fix your issue: https://benjaminhorn.io/code/wordpress-visual-editor-not-visible-because-of-user-agent-sniffing/
Hello Matthewjackowski,
this line creates an endless loop from https to http and back again. Ok i use varnish 5.2.1 , is the Code ok?
if ( std.port(server.ip) == 6080) {
set req.http.x-redir = "https://" + req.http.host + req.url;
return (synth(750, "Moved permanently"));
}
if you have a specific url to login
you have to find this and add the