# 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 = "127.0.0.1"; .port = "8080"; .connect_timeout = 1s; .first_byte_timeout = 30s; .between_bytes_timeout = 5s; .max_connections = 100; } acl purge { "localhost"; "127.0.0.1"; } sub vcl_recv { if (req.url ~ "phpmyadmin") { return (pass); } if (req.url ~ "/wp-(login|admin)") { return (pass); } # pass search URL if ( req.url ~ "^/\?s" ) { return (pass); } # pass search URL if ( req.url ~ "/search/" ) { return (pass); } # 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.Host = regsub(req.http.Host, ":[0-9]+", ""); # Unset headers that might cause us to cache duplicate infos unset req.http.Accept-Language; # Normalize Vary Header to keep different cache versions for desktop and mobile; if (req.http.User-Agent ~ "(Mobile|Android|iPhone|iPad)") { set req.http.User-Agent = "mobile"; } else { set req.http.User-Agent = "desktop"; } if (req.http.Authorization || req.method == "POST") { return (pass); } # Allow purge from allowed IPs only if (req.method == "PURGE") { if (!client.ip ~ purge) { # return(synth(405,"Not allowed.")); } return (purge); } # pass wp-admin urls if (req.url ~ "(wp-login|wp-admin|login|logout)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php") { return (pass); } # drop cookies and params from static assets if (req.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|ico|png|woff|woff2)(\?.*|)$") { 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, "\?.*$", ""); } # 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.=[^;]+(; )?", ""); # Remove the Quant Capital cookies (added by some plugin, all __qca) set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", ""); # Remove the wp-settings-1 cookie set req.http.Cookie = regsuball(req.http.Cookie, "wp-settings-=[^;]+(; )?", ""); 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-=[^;]+(; )?", ""); 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=[^;]+(; )?", ""); # Are there cookies left with only spaces or that are empty? if (req.http.cookie ~ "^ *$") { unset req.http.cookie; } # pass wp-admin cookies if (req.http.cookie) { if (req.http.cookie ~ "(wordpress|wordpress_)") { 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); } # Set TTL Explicitly if ignored from server if (beresp.ttl < 120s) { set beresp.ttl = 120s; unset beresp.http.Cache-Control; } if (beresp.status == 302){ set beresp.http.X-Cacheable = "NO: 302"; set beresp.uncacheable = true; return (deliver); } # Remove setcookies for static resources if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|ico|png|woff|woff2)(\?.*|)$") { unset beresp.http.set-cookie; unset beresp.http.cookie; } # unset set-cookies from backendresponse if url is not for login users if (!(bereq.url ~ "(wp-login|wp-admin|login|logout)")) { set beresp.http.X-UnsetCookies = "TRUE"; unset beresp.http.set-cookie; } if (bereq.http.Cookie ~ "(wordpress|wordpress_logged_in)") { # if we get a session cookie...caching is a no-go set beresp.http.X-Cacheable = "NO:Got Session"; set beresp.uncacheable = true; set beresp.ttl = 0s; return (deliver); } 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; set beresp.ttl = 0s; 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; set beresp.ttl = 0s; 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 = 365d; # marker for vcl_deliver to reset Age: set beresp.http.magicmarker = "1"; } # setting ttl to 1h for backendresponse if url is not for login users if (!(bereq.url ~ "(wp-login|wp-admin|login|logout)")) { set beresp.ttl = 1h; } # long ttl for assets if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|ico|png|woff|woff2)(\?.*|)$") { set beresp.http.cache-control = "max-age=604800"; set beresp.ttl = 365d; } set beresp.grace = 1h; } sub vcl_hash { # If the client supports compression, keep that in a different cache if (req.http.Accept-Encoding) { hash_data(req.http.Accept-Encoding); } } sub vcl_backend_error { # display custom error page if backend down if (beresp.status == 503 && bereq.retries == 3) { synthetic(std.fileread("/path/to/503.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("/path/to/503.html")); return(deliver); } } sub vcl_deliver { # oh 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 = "cached"; } else { set resp.http.X-Cache = "uncached"; } 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")); } } sub vcl_purge { return (synth(200, "Purged")); }