Varnish is a full-page cache and HTTP preprocessor which can be used to speed up requests for a WordPress site.
Setting up Varnish is a technical task, as it requires installing and configuring a system package on a web server, rather than just installing a plugin.
Here is an example default.vcl configuration for a WordPress site for Varnish 4.0. This assumes you have installed the Query String Varnish module.
# Drawn largely from the following.
# https://github.com/mattiasgeniar/varnish-4.0-configuration-templates/blob/master/default.vcl
# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;
# https://github.com/Dridi/libvmod-querystring
import querystring;
# Default backend definition. Set this to point to your content server.
backend www {
.host = "127.0.0.1";
.port = "9080";
.max_connections = 256;
}
# Define an access control list to restrict cache purging.
acl purge {
"127.0.0.1";
"192.168.0.0"/16;
}
sub vcl_hit {
if (obj.ttl >= 0s) {
# A pure unadulterated hit, deliver it
return (deliver);
}
if (obj.ttl + obj.grace > 0s) {
# Object is in grace, deliver it
# Automatically triggers a background fetch
return (deliver);
}
# fetch & deliver once we get the result
return (fetch);
}
sub vcl_recv {
# If this is a cache purge request, make sure the request is coming from a trusted actor.
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return(synth(405, "Not allowed."));
}
return (purge);
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PATCH" &&
req.method != "DELETE") {
return (pipe);
}
if (req.http.Upgrade ~ "(?i)websocket") {
return (pipe);
}
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
if (req.http.Authorization) {
return (pass);
}
# Bypass cache for WordPress admin page requests.
if (req.url~ "^/wp-admin/") {
return (pass);
}
# At this point, we're assuming we can't just pass or pipe the request and
# need to start thinking about how it might be cached.
if (req.url ~ "\#") {
set req.url = regsub(req.url, "\#.*$", "");
}
# For static file access, strip all querystring parameters.
if (req.url ~ "^[^?]*\.(bmp|bz2|css|doc|eot|flv|gif|gz|ico|jpe?g|js|less|mp[34]|otf|pdf|png|rar|rtf|swf|tar|tgz|ttf|txt|wav|webm|woff|xml|zip)(\?.*)?$") {
unset req.http.Cookie;
set req.url = querystring.remove(req.url);
return (hash);
}
# Whitelist query string parameters for WordPress.
set req.url = querystring.filter_except(req.url,
"sort" + querystring.filtersep() +
"q" + querystring.filtersep() +
"dom" + querystring.filtersep() +
"dedupe_hl" + querystring.filtersep() +
"filter" + querystring.filtersep() +
"attachment" + querystring.filtersep() +
"attachment_id" + querystring.filtersep() +
"author" + querystring.filtersep() +
"author_name" + querystring.filtersep() +
"cat" + querystring.filtersep() +
"calendar" + querystring.filtersep() +
"category_name" + querystring.filtersep() +
"comments_popup" + querystring.filtersep() +
"cpage" + querystring.filtersep() +
"day" + querystring.filtersep() +
"dedupe_hl" + querystring.filtersep() +
"dom" + querystring.filtersep() +
"error" + querystring.filtersep() +
"exact" + querystring.filtersep() +
"exclude" + querystring.filtersep() +
"feed" + querystring.filtersep() +
"hour" + querystring.filtersep() +
"m" + querystring.filtersep() +
"minute" + querystring.filtersep() +
"monthnum" + querystring.filtersep() +
"more" + querystring.filtersep() +
"name" + querystring.filtersep() +
"order" + querystring.filtersep() +
"orderby" + querystring.filtersep() +
"p" + querystring.filtersep() +
"page_id" + querystring.filtersep() +
"page" + querystring.filtersep() +
"paged" + querystring.filtersep() +
"pagename" + querystring.filtersep() +
"pb" + querystring.filtersep() +
"post_type" + querystring.filtersep() +
"posts" + querystring.filtersep() +
"preview" + querystring.filtersep() +
"q" + querystring.filtersep() +
"robots" + querystring.filtersep() +
"s" + querystring.filtersep() +
"search" + querystring.filtersep() +
"second" + querystring.filtersep() +
"sentence" + querystring.filtersep() +
"sort" + querystring.filtersep() +
"static" + querystring.filtersep() +
"subpost" + querystring.filtersep() +
"subpost_id" + querystring.filtersep() +
"taxonomy" + querystring.filtersep() +
"tag" + querystring.filtersep() +
"tb" + querystring.filtersep() +
"tag_id" + querystring.filtersep() +
"term" + querystring.filtersep() +
"tb" + querystring.filtersep() +
"url" + querystring.filtersep() +
"w" + querystring.filtersep() +
"withcomments" + querystring.filtersep() +
"withoutcomments" + querystring.filtersep() +
"year");
# Sort the querystring parameters, so different orders of the same produce a single cache object.
if (req.url ~ "\?") {
set req.url = querystring.sort(req.url);
}
if (req.http.cookie) {
if (req.url ~ "preview") {
# Keep WordPress cookies for preview
return (pass);
} else {
# drop all cookies.
unset req.http.cookie;
}
}
return (hash);
}
sub vcl_pipe {
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
}
return (pipe);
}
sub vcl_backend_response {
# If the back-end response is 500-level, don't bust the cached object.
if (beresp.status == 500 || beresp.status == 502 || beresp.status == 503 || beresp.status == 504) {
return (abandon);
}
# Don't allow the backend to set cookies for static file requests.
if (bereq.url ~ "^[^?]*\.(bmp|bz2|css|doc|eot|flv|gif|gz|ico|jpe?g|js|less|mp[34]|otf|pdf|png|rar|rtf|swf|tar|tgz|ttf|txt|wav|webm|woff|xml|zip)(\?.*)?$") {
unset beresp.http.set-cookie;
}
if (beresp.status == 301 || beresp.status == 302) {
set beresp.http.location = regsub(beresp.http.location, ":[0-9]+", "");
}
# Don't cache on these kind of requests.
if (beresp.ttl <= 0s || beresp.http.set-cookie || beresp.http.vary == "*") {
set beresp.ttl = 120s;
set beresp.uncacheable = true;
return (deliver);
}
set beresp.grace = 6h;
return (deliver);
}
sub vcl_deliver {
unset resp.http.server;
unset resp.http.via;
unset resp.http.x-powered-by;
unset resp.http.x-runtime;
unset resp.http.x-varnish;
return (deliver);
}