Using Varnish Cache with WordPress

Using Varnish Cache with WordPress

Varnish Cache is a power Caching proxy that you can use to help mitigate large amounts of traffic from hitting your http server (typically Apache, Lighttpd, or others). What Varnish does is keeps a cached copy of a URL in memory when it’s loaded, and then the next person to request that page, will receive the cached copy. You can set Varnish to then expire the page in cache after any amount of time you deem necessary.

For a little more about Varnish you can view their ‘About‘ section.

Where most people have an issue is setting up a good ‘VCL’ or Varnish Configuration Language. To me it looks like a mixture of PERL, C, and some custom formatting but overall, it can be confusing at times. Well, I’ve spent A LOT of time trying to configure Varnish to work with WordPress and today, I think I’ve finally done it. This configuration includes not caching wp-admin, when cookies are set, and if the person is viewing on a mobile or smartphone the hash key is set differently so that plugins like WPTouch will still work.

If you have changes or improvements let me know! The basics were taken from GitHub user mattiasgeniar in his list of Varnish 3.0 Examples

Using Varnish Cache with WordPress

backend default {
.host = “127.0.0.1”;
.port = “8080”;
}
# Called after a document has been successfully retrieved from the backend.
sub vcl_fetch {
set req.http.X-Forwarded-For = client.ip;

# Uncomment this if you want to block certain user agents
#if (req.http.user-agent ~ “yandex” || req.http.user-agent ~ “Yandex” || req.http.user-agent ~ “Baidu” || req.http.user-agent ~ “baidu” || req.http.user-agent ~ “Baidubot” || req.http.user-agent ~ “baidubot”) {
# error 403 “Forbidden, naughty naughty bot.”;
#}

# Get Device type (for caching hashes)
call detect_device;

# Add a www if there isn’t one so we only cache 1 version for www and non-www
if (req.http.host !~ “www.”) {
set req.http.host = regsub(req.http.host, “$”,”www.$”);
}

# Uncomment to make the default cache “time to live” is 5 minutes, handy
# but it may cache stale pages unless purged. (TODO)
# By default Varnish will use the headers sent to it by Apache (the backend server)
# to figure out the correct TTL.
# WP Super Cache sends a TTL of 3 seconds, set in wp-content/cache/.htaccess

set beresp.ttl = 24h;

# Strip cookies for static files and set a long cache expiry time.
if (req.url ~ “.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$”) {
unset beresp.http.set-cookie;
set beresp.ttl = 24h;
}

# If WordPress cookies found then page is not cacheable
if (req.http.Cookie ~”(wp-postpass|wordpress_logged_in|comment_author_)”) {
# set beresp.cacheable = false;#versions less than 3
#beresp.ttl>0 is cacheable so 0 will not be cached
set beresp.ttl = 0s;
} else {
# set beresp.cacheable = true;
set beresp.ttl=24h;#cache for 24hrs
}

# Varnish determined the object was not cacheable
#if ttl is not > 0 seconds then it is cachebale
if (!beresp.ttl > 0s) {
set beresp.http.X-Cacheable = “NO:Not Cacheable”;
} else if ( req.http.Cookie ~”(wp-postpass|wordpress_logged_in|comment_author_)” ) {
# You don’t wish to cache content for logged in users
set beresp.http.X-Cacheable = “NO:Got Session”;
return(hit_for_pass); #previously just pass but changed in v3+
} else if ( 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”;
return(hit_for_pass);
} else if ( beresp.ttl < 1s ) { # You are extending the lifetime of the object artificially set beresp.ttl = 300s; set beresp.grace = 300s; set beresp.http.X-Cacheable = “YES:Forced”; } else { # Varnish determined the object was cacheable set beresp.http.X-Cacheable = “YES”; } if (beresp.status == 404 || beresp.status >= 500) {
set beresp.ttl = 0s;
}

# Deliver the content
return(deliver);
}

sub vcl_hash {
# Each cached page has to be identified by a key that unlocks it.
# Add the browser cookie only if a WordPress cookie found.
if ( req.http.Cookie ~”(wp-postpass|wordpress_logged_in|comment_author_)” ) {
#set req.hash += req.http.Cookie;
hash_data(req.http.Cookie);
}

# And then add the device to the hash (if its a mobile device)
if (req.http.X-Device ~ “smart” || req.http.X-Device ~ “other”) {
hash_data(req.http.X-Device);
}
}

# Deliver
sub vcl_deliver {
# Uncomment these lines to remove these headers once you’ve finished setting up Varnish.
remove resp.http.X-Varnish;
remove resp.http.Via;
remove resp.http.Age;
remove resp.http.X-Powered-By;
}

# vcl_recv is called whenever a request is received
sub vcl_recv {
# remove ?ver=xxxxx strings from urls so css and js files are cached.
# Watch out when upgrading WordPress, need to restart Varnish or flush cache.
set req.url = regsub(req.url, “?ver=.*$”, “”);

# Remove “replytocom” from requests to make caching better.
set req.url = regsub(req.url, “?replytocom=.*$”, “”);

remove req.http.X-Forwarded-For;
set req.http.X-Forwarded-For = client.ip;

# Exclude this site because it breaks if cached
#if ( req.http.host == “example.com” ) {
# return( pass );
#}

# Serve objects up to 2 minutes past their expiry if the backend is slow to respond.
set req.grace = 120s;
# Strip cookies for static files:
if (req.url ~ “.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$”) {
unset req.http.Cookie;
return(lookup);
}
# Remove has_js and Google Analytics __* cookies.
set req.http.Cookie = regsuball(req.http.Cookie, “(^|;s*)(__[a-z]+|has_js)=[^;]*”, “”);
# Remove a “;” prefix, if present.
set req.http.Cookie = regsub(req.http.Cookie, “^;s*”, “”);
# Remove empty cookies.
if (req.http.Cookie ~ “^s*$”) {
unset req.http.Cookie;
}
if (req.request == “PURGE”) {
if (!client.ip ~ purgehosts) {
error 405 “Not allowed.”;
}
#previous version ban() was purge()
ban(“req.url ~ ” + req.url + ” && req.http.host == ” + req.http.host);
error 200 “Purged.”;
}

# Pass anything other than GET and HEAD directly.
if (req.request != “GET” && req.request != “HEAD”) {
return( pass );
} /* We only deal with GET and HEAD by default */

# remove cookies for comments cookie to make caching better.
set req.http.cookie = regsub(req.http.cookie, “1231111111111111122222222333333=[^;]+(; )?”, “”);

# never cache the admin pages, or the server-status page, or your feed? you may want to..i don’t
if (req.request == “GET” && (req.url ~ “(wp-admin|bb-admin|server-status|feed)”)) {
return(pipe);
}
# don’t cache authenticated sessions
if (req.http.Cookie && req.http.Cookie ~ “(wordpress_|PHPSESSID)”) {
return(lookup);
}
# don’t cache ajax requests
if(req.http.X-Requested-With == “XMLHttpRequest” || req.url ~ “nocache” || req.url ~
“(control.php|wp-comments-post.php|wp-login.php|bb-login.php|bb-reset-password.php|register.php)”) {
return (pass);
}
return( lookup );
}

#set of hosts/users from which purging can be done
acl purgehosts {
“localhost”;
}

# Detect the device
sub detect_device {
# Define the desktop device
set req.http.X-Device = “desktop”;

if (req.http.User-Agent ~ “iP(hone|od)” || req.http.User-Agent ~ “Android” || req.http.User-Agent ~ “iPad”) {
# Define smartphones and tablets
set req.http.X-Device = “smart”;
}

elseif (req.http.User-Agent ~ “SymbianOS” || req.http.User-Agent ~ “^BlackBerry” || req.http.User-Agent ~ “^SonyEricsson” || req.http.User-Agent ~ “^Nokia” || req.http.User-Agent ~ “^SAMSUNG” || req.http.User-Agent ~ “^LG”) {
# Define every other mobile device
set req.http.X-Device = “other”;
}
}