Boost WordPress performance with Varnish Cache
Lets face it, WordPress is slow. With every request it has to go though thousands lines of code and multiple SQL queries to render a page. Very popular configuration for a WordPress site is Apache, mod_rewrite, mod_php, PHP and MySQL. It’s very good setup but can’t be consider the fastest (at least without any additional tweaking).
The good news is WordPress doesn’t have to be a speed demon. In most cases it’s just a CMS to produce static pages. If the content is static it doesn’t make any sense to waste CPU cycles on re-rendering the same HTML over and over again.
Currently I’m running this blog on Amazon EC2 Micro instance. It’s the smallest (and slowest) setup you can get from Amazon. The micro instance has 613 MB of RAM and a limited access to CPU. When I tried to benchmark it with the Apache Benchamark I got about 1 req/sec… and crashed database. Ten concurrent connections were enough to DoS my blog! Fifteen minutes later which was required to setup Varnish Cache the same hardware could handle 665 req/sec. During the test CPU got to 65% which means I reached broadband limits.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ ab -c300 -n500 http://systemsarchitect.net/ Server Software: Apache/2.2.22 Server Hostname: systemsarchitect.net Server Port: 80 Document Path: / Document Length: 128174 bytes Concurrency Level: 300 Time taken for tests: 0.751 seconds Complete requests: 500 Failed requests: 0 Write errors: 0 Total transferred: 64310000 bytes HTML transferred: 64087000 bytes Requests per second: 665.85 [#/sec] (mean) Time per request: 450.555 [ms] (mean) Time per request: 1.502 [ms] (mean, across all concurrent requests) Transfer rate: 83633.83 [Kbytes/sec] received |
So why to make a website fast? There are at least 3 reasons:
- It will survive sudden traffic spikes (so called Slashdot effect)
- better Google ranking
- according to Google’s research there is a correlation between website’s response time and consumed content. In other words the faster website is the higher chance for another click.
Varnish Cache is a web application accelerator also known as a caching HTTP reverse proxy. You install it in front of any server that speaks HTTP and configure it to cache the contents. Varnish Cache is really, really fast. It typically speeds up delivery with a factor of 300 – 1000x, depending on your architecture.
Varnish stands in front of a web server which means it will have to listen on port 80. By default this port it already taken by Apache. The first thing is to change configuration of your web server.
1 |
$ vim /etc/apache2/ports.conf |
Change port 80 to 8080.
1 2 3 4 5 6 7 8 |
NameVirtualHost *:8080 Listen 8080 <IfModule mod_ssl.c> Listen 443 </IfModule> <IfModule mod_gnutls.c> Listen 443 </IfModule> |
Edit virtual host settings.
1 |
$ vim /etc/apache2/sites-available/architect |
Alter port from 80 to 8080.
1 |
<VirtualHost *:8080> |
Now install varnish. Depends on your distribution you might get different version of the software. This article is created for varnish 3.x.
1 2 3 4 5 |
$ apt-get install varnish $ varnishd -V varnishd (varnish-3.0.2 revision cbf1284) Copyright (c) 2006 Verdens Gang AS Copyright (c) 2006-2011 Varnish Software AS |
Edit varnish settings and set port and cache size.
1 |
$ vim /etc/default/varnish |
Memory on my server is limited so I use only 64 MB. Default value is 254 but quoter of that is enough for a small blog.
1 2 3 4 5 |
DAEMON_OPTS="-a :80 \ -T localhost:6082 \ -f /etc/varnish/default.vcl \ -S /etc/varnish/secret \ -s malloc,64m" |
Edit varnish configuration.
1 |
$ vim /etc/varnish/default.vcl |
Change the VCL script to
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
backend default { .host = "127.0.0.1"; .port = "8080"; } sub vcl_recv { if (req.restarts == 0) { if (req.http.x-forwarded-for) { set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; } else { set req.http.X-Forwarded-For = client.ip; } } if (req.request == "PURGE") { if ( client.ip != "54.246.44.13") { error 405 "Not allowed."; } return (lookup); } if (req.request != "GET" && req.request != "HEAD" && req.request != "PUT" && req.request != "POST" && req.request != "TRACE" && req.request != "OPTIONS" && req.request != "DELETE") { return (pipe); } if (req.request != "GET" && req.request != "HEAD") { return (pass); } if (!(req.url ~ "wp-(login|admin)") && !(req.url ~ "&preview=true" ) ) { unset req.http.cookie; } if (req.http.Authorization || req.http.Cookie) { return (pass); } return (lookup); } sub vcl_hit { if (req.request == "PURGE") { purge; error 200 "Purged."; } return (deliver); } sub vcl_miss { if (req.request == "PURGE") { purge; error 200 "Purged."; } return (fetch); } sub vcl_fetch { if (!(req.url ~ "wp-(login|admin)")) { unset beresp.http.set-cookie; set beresp.ttl = 96h; } if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") { set beresp.ttl = 120 s; return (hit_for_pass); } return (deliver); } |
You will have to change the IP 54.246.44.13 to your server’s address.
Everything is in place now so the last thing to do is restartng services.
1 2 |
$ sudo /etc/init.d/apache2 restart $ sudo /etc/init.d/varnish restart |
To make sure caching is working run tail against all apache logs and refresh your website few times.
1 |
$ tail -f /var/log/apache2/* |
First request will hit Apache server and you should see new entries in the log. Varnish will put all resources for that URL in cache. Every following request shouldn’t populate any new logs.
The VCL configuration makes Varnish cache every GET request for 96 hours. There is an exception for wp-login and wp-admin. Those are dynamic pages and it’s better not to cache them. Your visitors have no reason to go there anyway.
Varnish will cache everything for 96h. If you create a new post or edit an existing one your changes won’t be visible. Varnish doesn’t know something has changed and will continue serving stale content. You can restart the service to wipe all data.
1 |
$ /etc/init.d/varnish restart |
It’s not very user friendly approach. It also clears more then it should. Don’t worry, it’s WordPress. There is plugin for everything. Install “Varnish HTTP Purge” extension. No configuration is required. Default.vcl file already handlers PURGE requests. Now you can visit your blog, open network tab in Firebug and enjoy response time below 200ms.
Varnish is an amazing peace of software with very powerful features. It can act as a load balancer, it can pull different parts of your website from different palaces (ESI) and if it can’t do something there might be an extension for that. There is nothing to wait for. Download it. Use it.
3 Comments
Guilherme Euler
24/12/2013Hello friend,
When I try to start varnish with this VCL I get the following error:
Message from VCC-compiler:
Expected variable, string or semicolon
(input Line 10 Pos 38)
req.http.X-Forwarded-For + “, ” + client.ip;
————————————-#——————
Running VCC-compiler failed, exit 1
VCL compilation failed
What’s happening?
Ian Chard
23/06/2014Great stuff — thanks so much for this, it saved me having to figure out the cookie faff to make WordPress admin happy.
I added one more test to bypass varnish for the Jetpack site monitor, so that it actually tests the back end:
if (req.http.User-Agent ~ “^jetmon/”) {
/* Don’t cache so that jetpack can monitor backend health */
return (pass);
}
Prasenjit
11/07/2014Hi Lucasz,
Thanks for the configs. I am new to Varnish and am trying to configure it on a test site. I can see that the Varnish headers are being added, but when I do “varnishstat”, I get the below output. Hit ratio and hitrate avg is always 0. Can you point me to the right direction what the below output means?
Thanks.