Mike Green
Dec 17, 2009 · 4 min read

A while ago my blog started to act up by randomly showing translated pages in place of the desired language. The culprit was a Wordpress caching plugin (Hyper Cache) that started to misbehave with the latest upgrade. I promptly disabled it and went on a search for a replacement.

As you may have read in one of my previous blog entries, specifically “NginX and Apache, but no memcached”, I prefer to use NginX as the front-end serving static files, and Apache as a back-end dealing with the dynamic pages. So it would be ideal if NginX could serve up static Wordpress files, which is exactly what I am doing now with the help of WP Super Cache.

WP Super Cache is a rather popular plugin and converts the output generated by WordPress into a static HTML file. Installation is quick and painless, and automates a few tasks for you (or lets you know what needs to be changed on your website).

To make it work with a NginX front-end and Apache back-end, a few additional changes need to be made, primarily to the NginX configuration.

Wordpress / Apache Changes

Firstly, enable WP Super Cache into the “full on” setting, meaning it generates a “cache” and a “super cache”. WP Super Cache will then ask you to change the .htaccess file, by adding a few Rewrite Rules. In this case, we do not need those entries and we can reduce it to just the following:

# BEGIN WPSuperCache
### WARNING: This is handled by NginX!
# END WPSuperCache

This will keep the plugin’s setting page from complaining about the need to configure .htaccess.

I have also enabled the “Super Cache Compression” option, however this is not mandatory. I choose to do this to relieve NginX from a few CPU cycles (as the HTML is already compressed then).

NginX Changes

Next the NginX website configuration needs to be modified. And a few of these modifications will depend on your website or needs. In my case, I am using a separate template for the iPhone, Android and other smart phones and do not wish to use the cache for these. So I have the following entry in my NginX that will detect mobile phones:

server {... (snip!) ...# Check if it's a mobile phone
set $mobile "";
if ($http_user_agent ~* "(2.0 MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo Wii|Nitro|Nokia|Opera Mini|Palm|PlayStation Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows CE|WinWAP|YahooSeeker/M1A1-R2D2|NF-Browser|iPhone|iPod|Android|BlackBerry9530|G-TU915 Obigo|LGE VX|webOS|Nokia5800)" ) {
set $mobile "M";
}
... (snip!) ...

This will set the variable $mobile to “M” if the user-agent is from a mobile web browser. If you don’t need or want this, then you can simply leave it out of your NginX configuration.

We also need to modify the common location section. There are a number of ways to approach this, but I choose to use a number of “if” statements to build a variable $wpsc_flags. Depending on the list of “if” statements NginX will output the static cached file from WP Super Cache or continue to the Apache back-end (which will handle the rest).

Unfortunately NginX does not provide support for nested “if” statements or booleans, so this is a slightly dirty hack:

location / {
# If it’s a POST request, send it directly backend:
if ($request_method = POST) {
access_log off;
proxy_pass http://apache_backend;
break;
}
# Is it a mobile user?
set $wpsc_flags “${mobile}”;
# Is the user logged in?
if ($http_cookie ~* “(comment_author_|wordpress_logged_in_|wp-postpass_)” ) {
set $wpsc_flags “${wpsc_flags}C”;
}
# Do we have query arguments?
if ($is_args) {
set $wpsc_flags “${wpsc_flags}Q”;
}
# Does the (gzip) Super Cache exist?
if (-f $document_root/wp-content/cache/supercache/$host/$uri/index.html.gz) {
# The file exists in the WP Super Cache
set $wpsc_flags “${wpsc_flags}F”;
}
# If the following flags are set (in this order) we use the cached version:
if ($wpsc_flags = “F”) {
expires 1h;
rewrite ^(.*)$ /wp-content/cache/supercache/$host/$uri/index.html.gz break;
}
# Or else it goes to the backend:
access_log off;
proxy_pass http://apache_backend;
}

By reading the comments it should be clear what this does, but I’ll clarify further just in case.

A HTTP POST should not be cached, and so it will be sent directly to the Apache back-end server(s) and stops doing any other checking.

In the next step, it creates a $wpsc_flags variable based on the $mobile variable we created earlier. If you do not use the $mobile variable, then you can simply replace it as following:

set $wpsc_flags “”;

Then it will add flags to $wpsc_flags depending on whether the current user is logged in, if the request contains arguments (ie.: “someurl.com?this=is&an=argument”). This will prevent an editor or commenter from being presented cached pages, which can be bothersome.

Now, as I had turned on the “Super Cache Compression” in the Wordpress plugin, I am looking for files that end with “.html.gz”. If you are not using the compression option, simply remove the “.gz” from the “if (-f” statement as well as the “rewrite” statement.

Also note that it will only display the cached data if, and only if, $wpcs_flags is set to “F”. If you were logged in, this variable would actually be “CF” (in this order!) and so it will continue to the portion where everything is sent to the Apache back-end.

Speed Improvements

A while ago I had posted a message on a forum that showed how a Wordpress cache could improve responsiveness. The (truncated) results of a simple “ab -n 1000 -c 50 http://<website>” (ApacheBench) showed the following back then:

Requests per second: 1753.08 [#/sec] (mean)
Time per request: 28.521 [ms] (mean)
Time per request: 0.570 [ms] (mean, across all concurrent requests)

The cached responses were generated by Hyper Cache at the Apache back-end, then forwarded to NginX to be delivered to the user (“ab” in this case).
With the new setup, where NginX is responsible for the delivery of a cached response opposed to Apache / Wordpress:

Requests per second: 4712.65 [#/sec] (mean)
Time per request: 10.610 [ms] (mean)
Time per request: 0.212 [ms] (mean, across all concurrent requests)

Although these are not conclusive, lab-certified benchmarks, the crude test does show a rather impressive improvement. The time for each request has been reduced by more than half. So it’s well worth the effort.

Myatu’s

Myatu’s Tech Blog, from the site that has been wasting bits and bytes daily, since 2008.

Mike Green

Written by

I keep servers happy, and they keep me happy.

Myatu’s

Myatu’s

Myatu’s Tech Blog, from the site that has been wasting bits and bytes daily, since 2008.

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