Configuring Varnish for WordPress
WordPress is by far the most popular content management system on the internet. It is no longer exclusively used for simple blogs, but serves as the foundation of many popular websites.
In order to maintain the desired performance under heavy load, a solid caching strategy is a must. And that’s where Varnish truly shines.
In this tutorial we’ll explain how to install and configure Varnish on your WordPress site.
1. Install and configure Varnish
If you are already running WordPress in production, and you’re looking to accelerate the website with Varnish, you’ll have to decide where to install Varnish:
- You can install Varnish on a dedicated machine and point your DNS records to that server.
- You can install Varnish on the same server as your WordPress installation.
For a detailed step-by-step Varnish installation guide, we’d like to refer you to one of the following dedicated tutorials:
- Installing Varnish on Ubuntu
- Installing Varnish on Debian
- Installing Varnish on CentOS
- Installing Varnish on Red Hat Enterprise Linux
2. Reconfigure the web server port
The web server that is hosting your WordPress site is most likely listening for incoming HTTP requests on port 80. In order for Varnish caching to properly work, Varnish needs to be listening on port 80. This also means that your web server needs to be configured on another port. We’ll use port 8080 as the new web server listening port.
Depending on the type of web server you’re using, different configuration files need to be modified. Here’s a quick how-to for Apache and Nginx.
Apache
If you’re using Apache as your web server you need to replace Listen 80
with Listen 8080
in Apache’s main configuration file.
The individual virtual hosts will also contain port information. You will need to replace <VirtualHost *:80>
with <VirtualHost *:8080>
in all virtual host files.
Here’s how to change Apache’s listening port for various Linux distributions:
- Change Apache’s listening port on Ubuntu
- Change Apache’s listening port on Debian
- Change Apache’s listening port on CentOS
- Change Apache’s listening port on Red Hat Enterprise Linux
These changes will only take effect once Apache is restarted.
Nginx
If you’re using Nginx, you’ll only have to replace listen 80;
with listen 8080;
in all virtual host files.
Here’s how to change Nginx’s listening port for various Linux distributions:
- Change the Nginx listening port on Ubuntu
- Change the Nginx listening port on Debian
- Change the Nginx listening port on CentOS
- Change the Nginx listening port on Red Hat Enterprise Linux
These changes will only take effect once Nginx is restarted.
3. Install a Varnish plugin for WordPress
Making sure that WordPress content ends up in the cache is Varnish’s responsibility; however, removing outdated content from the cache has to be triggered by WordPress.
There’s a whole range of Varnish plugins for WordPress, but in this tutorial we’re using the Proxy Cache Purge plugin.
- Go to the WordPress dashboard
- Click on Plugins
- Click on Add New
- Search for the Proxy Cache Purge plugin
- Click on Install Now and confirm
- Finally, activate the plugin
This plugin will purge content from the cache by sending an HTTP PURGE
request to Varnish. The logic that is responsible for purging the cache is defined in custom VCL logic, which is defined in the next step.
4. Custom WordPress VCL
A custom VCL file containing the necessary caching rules is needed to guarantee decent performance. This file is located in /etc/varnish/default.vcl
and also contains the backend definition that Varnish uses to connect to the web server.
The WordPress VCL file
Here’s the complete VCL file you can use:
vcl 4.1;
import std;
backend default {
.host = "127.0.0.1";
.port = "8080";
}
# Add hostnames, IP addresses and subnets that are allowed to purge content
acl purge {
"localhost";
"127.0.0.1";
"::1";
}
sub vcl_recv {
# Remove empty query string parameters
# e.g.: www.example.com/index.html?
if (req.url ~ "\?$") {
set req.url = regsub(req.url, "\?$", "");
}
# Remove port number from host header
set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
# Sorts query string parameters alphabetically for cache normalization purposes
set req.url = std.querysort(req.url);
# Remove the proxy header to mitigate the httpoxy vulnerability
# See https://httpoxy.org/
unset req.http.proxy;
# Add X-Forwarded-Proto header when using https
if (!req.http.X-Forwarded-Proto) {
if(std.port(server.ip) == 443 || std.port(server.ip) == 8443) {
set req.http.X-Forwarded-Proto = "https";
} else {
set req.http.X-Forwarded-Proto = "http";
}
}
# Purge logic to remove objects from the cache.
# Tailored to the Proxy Cache Purge WordPress plugin
# See https://wordpress.org/plugins/varnish-http-purge/
if(req.method == "PURGE") {
if(!client.ip ~ purge) {
return(synth(405,"PURGE not allowed for this IP address"));
}
if (req.http.X-Purge-Method == "regex") {
ban("obj.http.x-url ~ " + req.url + " && obj.http.x-host == " + req.http.host);
return(synth(200, "Purged"));
}
ban("obj.http.x-url == " + req.url + " && obj.http.x-host == " + req.http.host);
return(synth(200, "Purged"));
}
# Only handle relevant HTTP request methods
if (
req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "PATCH" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE"
) {
return (pipe);
}
# Remove tracking query string parameters used by analytics tools
if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "");
set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?");
set req.url = regsub(req.url, "\?&", "?");
set req.url = regsub(req.url, "\?$", "");
}
# Only cache GET and HEAD requests
if (req.method != "GET" && req.method != "HEAD") {
set req.http.X-Cacheable = "NO:REQUEST-METHOD";
return(pass);
}
# Mark static files with the X-Static-File header, and remove any cookies
# X-Static-File is also used in vcl_backend_response to identify static files
if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") {
set req.http.X-Static-File = "true";
unset req.http.Cookie;
return(hash);
}
# No caching of special URLs, logged in users and some plugins
if (
req.http.Cookie ~ "wordpress_(?!test_)[a-zA-Z0-9_]+|wp-postpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+|wordpress_logged_in_|comment_author|PHPSESSID" ||
req.http.Authorization ||
req.url ~ "add_to_cart" ||
req.url ~ "edd_action" ||
req.url ~ "nocache" ||
req.url ~ "^/addons" ||
req.url ~ "^/bb-admin" ||
req.url ~ "^/bb-login.php" ||
req.url ~ "^/bb-reset-password.php" ||
req.url ~ "^/cart" ||
req.url ~ "^/checkout" ||
req.url ~ "^/control.php" ||
req.url ~ "^/login" ||
req.url ~ "^/logout" ||
req.url ~ "^/lost-password" ||
req.url ~ "^/my-account" ||
req.url ~ "^/product" ||
req.url ~ "^/register" ||
req.url ~ "^/register.php" ||
req.url ~ "^/server-status" ||
req.url ~ "^/signin" ||
req.url ~ "^/signup" ||
req.url ~ "^/stats" ||
req.url ~ "^/wc-api" ||
req.url ~ "^/wp-admin" ||
req.url ~ "^/wp-comments-post.php" ||
req.url ~ "^/wp-cron.php" ||
req.url ~ "^/wp-login.php" ||
req.url ~ "^/wp-activate.php" ||
req.url ~ "^/wp-mail.php" ||
req.url ~ "^/wp-login.php" ||
req.url ~ "^\?add-to-cart=" ||
req.url ~ "^\?wc-api=" ||
req.url ~ "^/preview=" ||
req.url ~ "^/\.well-known/acme-challenge/"
) {
set req.http.X-Cacheable = "NO:Logged in/Got Sessions";
if(req.http.X-Requested-With == "XMLHttpRequest") {
set req.http.X-Cacheable = "NO:Ajax";
}
return(pass);
}
# Remove any cookies left
unset req.http.Cookie;
return(hash);
}
sub vcl_hash {
if(req.http.X-Forwarded-Proto) {
# Create cache variations depending on the request protocol
hash_data(req.http.X-Forwarded-Proto);
}
}
sub vcl_backend_response {
# Inject URL & Host header into the object for asynchronous banning purposes
set beresp.http.x-url = bereq.url;
set beresp.http.x-host = bereq.http.host;
# If we dont get a Cache-Control header from the backend
# we default to 1h cache for all objects
if (!beresp.http.Cache-Control) {
set beresp.ttl = 1h;
set beresp.http.X-Cacheable = "YES:Forced";
}
# If the file is marked as static we cache it for 1 day
if (bereq.http.X-Static-File == "true") {
unset beresp.http.Set-Cookie;
set beresp.http.X-Cacheable = "YES:Forced";
set beresp.ttl = 1d;
}
# Remove the Set-Cookie header when a specific Wordfence cookie is set
if (beresp.http.Set-Cookie ~ "wfvt_|wordfence_verifiedHuman") {
unset beresp.http.Set-Cookie;
}
if (beresp.http.Set-Cookie) {
set beresp.http.X-Cacheable = "NO:Got Cookies";
} elseif(beresp.http.Cache-Control ~ "private") {
set beresp.http.X-Cacheable = "NO:Cache-Control=private";
}
}
sub vcl_deliver {
# Debug header
if(req.http.X-Cacheable) {
set resp.http.X-Cacheable = req.http.X-Cacheable;
} elseif(obj.uncacheable) {
if(!resp.http.X-Cacheable) {
set resp.http.X-Cacheable = "NO:UNCACHEABLE";
}
} elseif(!resp.http.X-Cacheable) {
set resp.http.X-Cacheable = "YES";
}
# Cleanup of headers
unset resp.http.x-url;
unset resp.http.x-host;
}
Customizations
The VCL file for WordPress contains two sections that might require some customization:
- The backend definition
- The access control list (ACL)
Here’s the backend definition:
backend default {
.host = "127.0.0.1";
.port = "8080";
}
127.0.0.1
on port 8080
when content needs to be fetched. Assuming that Varnish is hosted on the same server as WordPress, this value can be left unchanged.If you’re hosting WordPress on another server or on another port, you’ll need to modify the .host
and .port
values.
The current values inside the purge
access control list (ACL) all refer to the local machine, as you can see in the snippet below:
acl purge {
"localhost";
"127.0.0.1";
"::1";
}
If invalidations happen from external locations, the IP addresses, the IP ranges, or the host names of these locations have to be added to the ACL.
5. Restart the services
If you’re using Apache as a web server, you’ll run the following command to restart it:
sudo systemctl restart apache2
If you’re using Nginx instead, please run the following command to restart your web server:
sudo systemctl restart nginx
And finally, you’ll have to run the following command to restart Varnish:
sudo systemctl restart varnish
After the restart, your web server will accept traffic on port 8080
, Varnish will handle HTTP traffic on port 80
. The restart will also ensure that the correct VCL file is loaded, which will ensure that requests for your WordPress website can be properly cached.