Setting Up Varnish-Cache 4 for WordPress

Speed up your WordPress site

Eni Sinanaj
Dec 8, 2019 · 6 min read
Photo by Annie Spratt on Unsplash

As described in a previous piece, I’ve been using Varnish to improve the speed and reliability of a high-traffic website, while reducing server costs at the same time:

What is Varnish-Cache?

Varnish is a tool that serves as a full-page cache mechanism. It can be set up as a reverse proxy — before it asks the back end system for a certain HTTP request, it makes a cache check on its own cache storage. Given a cache hit, it will return the exact full HTML page it had stored previously while for a miss, it will ask one of the back end endpoints to serve the request, store it, and then return it to the requesting user.


Varnish performs extremely well. It is usually bound by the speed of the network, effectively turning performance into a non-issue. We’ve seen Varnish delivering 20 Gbps on regular off-the-shelf hardware.

How to Set It Up for WordPress

As with every application, there are some requests that can be cached completely while other requests can’t be cached at all.

Depending on the case, there may be get requests that can’t be cached. Generally, on a WordPress site, all get requests can be cached.

For WordPress, what should not be cached are the API endpoints, which are under path /wp-json/, and of course, all POST requests. I found out there are some plugins that need not use any caching — Elementor, for example.

The retention period of the cache obviously depends on the website and how often content changes. In my instance, I created a simple plugin for WordPress hooked at post/page update which will clear the cache for me. The right thing to do would be that of clearing the cache for only that entry that has just been updated. I didn’t get that far and just deleted all the cache at every new article or article update.


We have three actors here: WordPress, Varnish-Cache reverse proxy, and the web server, which in my case is Nginx.

First, we need to set up Varnish-Cache as a reverse proxy, without caching anything.

Set up backend in Varnish-Cache

backend default {
.host = "";
.port = "8080";

In my configuration, I have all the instances in an Azure network and the Web Server has the IP address10.0.0.5.

Handle requests

sub vcl_recv {

return(pass); will ignore caching and just delegate the request to the backend. With these two blocks, Varnish-Cache is set up as a reverse proxy and will always ask the backend for every request.

Full file /etc/varnish/default.vcl:

NGinX set up

The only thing to update on the NGinX web server configuration is changing the port it listens to.

server {


listen 8080;

SSL Setup

I’m managing my domains with Cloudflare and it would certainly be sufficient to configure just Flexible SSL on the Cloudflare admin. Although I configured it as Full. The difference between Flexible and Full is that the first will assure an SSL connection between the client and Cloudflare while between Cloudflare and your server the connection will not use SSL. Full, on the other hand, means that both legs of this connection will use SSL.

For this reason, I set up my web server as the first responder, which serves just as an SSL endpoint for the handshake to go through. This will then forward the request to Varnish-Cache which, eventually (on a MISS), forwards it to the back end (webserver).

This is the configuration:

server {  server_name * ....;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header Host $host;
# Certbot will eventually take care of the following if you
# generate the SSL certificate with it
listen 443 ssl;
ssl_certificate path_to_cetificate/fullchain.pem;
ssl_certificate_key path_to_private_key/privkey.pem;

Internally, in my Azure network, the connections will not use SSL, therefore this server block will forward the calls to the Varnish-Cache instance at on port 80.

It will also forward all HTTPS related headers for WordPress to work.

Make WordPress work with the above configuration

Add the following at the end of wp-config.php file, on the root of your WordPress installation:

if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) {

This will prevent WordPress from making infinite redirects to an “https://” protocol because it doesn’t understand that the current request is already over https.

Enable Caching

Now that we have all the basic pieces in place, we can configure Varnish to actually do some caching of the requests.

First of all let’s add a few blocks for the requests we want to be excluded from caching:

sub vcl_recv {  # Exclude caching Ajax requests
if (req.http.X-Requested-With == "XMLHttpRequest") {
# Exclude all POST requests or Authorization requests
if (req.http.Authorization || req.method == "POST") {
return (pass);
# Exclude everything that is neither GET nor HEAD
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
# Exclude everything related to the backed, using a
# Regular Expression we can match the url against
# wp-admin, post.php, edit.php, wp-login, wp-json.

if (req.url ~ "(wp-admin|post\.php|edit\.php|wp-login|wp-json)") {
# Exclude wp-cron or when the front end is being
# previewed from the administrator/developer

if (req.url ~ "/wp-cron.php" || req.url ~ "preview=true") {
return (pass);
# Exclude explicitly a specific file when requested
# from a specific host_name

if (( ~ "" && req.url ~ "^specific_file_name\.(css|js)")) {
return (pass);

For the other requests that will actually be cached, the following should apply:

sub vcl_recv {  ...  # Remove the has_js cookie
set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
# Remove the wp-settings-1 cookie
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-1=[^;]+(; )?", "");
# Remove the wp test cookie
set req.http.Cookie = regsuball(req.http.Cookie, "wordpress_test_cookie=[^;]+(; )?", "");
# Remove the PHPSESSID in members area cookie
set req.http.Cookie = regsuball(req.http.Cookie, "PHPSESSID=[^;]+(; )?", "");
unset req.http.Cookie;}

Clean the cache

The last step is making it possible to clean the cache from a specific HTTP request:

sub vcl_recv {  if (req.method == "PURGE") {
return (purge);
if (req.method == "CLEANFULLCACHE") {
ban(" ~ .*");
return (synth(200, "Full cache cleared"));

With these two conditions, we can easily clear the full cache.

A CURL call to invoke the cleaning would look like this curl -XCLEANFULLCACHE http://varnishurl_or_ip.

Full /etc/varnish/default.vcl

WordPress Automatic Cache Renew

The final piece of the puzzle is making WordPress clear the cache when a new article/page is create, or an existing entity is updated.

To do this, create a new folder inside [wordpress-root-installation]/wp-content/plugins/ e.g. [wordpress-root-installation]/wp-content/plugins/cachecleaner.php.

Create a new file inside the folder just created. We can call that cachcleaner.php.

Add the following code to the file and subsequently enable the new plugin from the WordPress admin panel.

Full file cachecleaner.php

Change the Varnish-Cache address and the WordPress Plugin information too, according to your own needs.

Better Programming

Advice for programmers.

Eni Sinanaj

Written by

#tech #startup #entrepreneur #business #money #excess #earth #motivational #speaker #hype #manager #startup #consulting

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade