-
-
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")); | |
} | |
} |
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.
Watch out for this booby trap!
set resp.http.Access-Control-Allow-Origin = "*";
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.
Hi! I would add this for certbot passthrough (for people using Let's Encrypt SSL/TLS):
if (req.url ~ "^/\.well-known/acme-challenge/") {
return (pass);
}
Is that VCL compatible with the latest wordpress and woocommerce?
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
if you have a specific url to login
you have to find this and add the
# unset cookies from backendresponse
if (!(bereq.url ~ "(wp-login|wp-admin| your specific url to login here)")) {
set beresp.http.X-UnsetCookies = "TRUE";
unset beresp.http.set-cookie;
set beresp.ttl = 1h;
}
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"));
}
Hey, @xoroz, I had the same problem with the WordPress visual editor, But adding the code snippet as the very first condition to evaluated in vcl_recv fixed it for me.
Pasting the same code snippet here for reference.
Hope it helps.