A working Varnish 4 config for WordPress 4.4

Adapting my extremely crufty Varnish 3.x config to Varnish 4 took most of a weekend. I found a great VCL3-to-4 script on github that did a lot of the heavy lifting, but there were a lot of little things to chase down and fix; the VCL syntax between versions 3 and 4 is juuuust different enough to cause headaches. One of the major stumbling blocks was adapting the WordPress configuration that I’d been using forever and ever; it turns out that revisiting it was a really good idea, because I’m a lot happier with WordPress VCL now than I was before.

First, props to Nadir Latif, since his VCL 4 WordPress configuration is just about perfect. I used that as a starting point and was able to get about 99% of the way to a working VCL4.1 configuration, but I was running into a weird error when logging into WordPress where the wp-login page would throw an error about my browser not accepting cookies (specifically, for you folks searching on the error’s text, it’s “Cookies are blocked or not supported by your browser. You must enable cookies to use WordPress.”

I suspected the reason behind the error was Varnish-related, and it turns out I was right. The cause is explained by Doug Sparling on page three of this two-year old wordpress.org thread. Very quickly, a quirk in the vcl_backend_response subroutine in Latif’s VCL (on line 89, specifically) was causing WordPress’s cookie check to fail and blame the browser, even though it was the server’s fault. In Sparling’s words from the thread:

Now for the quick explanation. In WordPress 3.6.1 (and earlier I assume), the req.request == "GET" part of Varnish config was preventing wp-login.php from writing the test cookie when the form was loaded (called with a GET), but the code that checked if the test cookie was actually set (when wp-login.php called via a POST) never ran if the user’s credentials were good…

In WordPress 3.7, this bug in wp-login.php was fixed, moving the redirect to wp-admin on successful login after the cookie check code above. So now the cookie check is actually run. So if Varnish is deleting the test cookie on GET request to the wp-login.php, this won’t prevent a login with WordPress 3.6.1 or older, but now that WordPress is actually checking for the test cookie, it will fails the cookie check as it should.

Here, then, is the VCL I wound up using in production, which is essentially Latif’s VCL with some host-specific changes and the one tweaked line:

sub vcl_recv {
...
   if (req.http.host ~"your.siteurl.com") {
      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, "\?.*$", "");
      }
      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, "\?.*$", "");
      }
      if (req.url ~ "wp-(login|admin)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php") {
         return (pass);
      }
      if (req.http.cookie) {
         if (req.http.cookie ~ "(wordpress_|wp-settings-)") {
            return(pass);
         } else {
            unset req.http.cookie;
         }
      }
   }
   ...
   return (hash)
}

...

sub vcl_backend_response {
...
   if ( (!(bereq.url ~ "(wp-(login|admin)|login)")) ) {
      unset beresp.http.set-cookie;
      set beresp.ttl = 1h;
   }
   if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") {
      set beresp.ttl = 365d;
   }
   ...
   return(deliver);
}

Restart Varnish and you’re all set!