Right, before I get started: This is an article aimed at a more advanced developer or perhaps even a sysadmin. This is for someone who likes tinkering with NGINX config files, likes to avoid using bloated plugins and wants to make their site really, really fast.

NGINX has a built in FastCGI cache that caches the result of the FastCGI process. It’s relatively simple to configure and there are lots of advantages to using NGINX’s FastCGI cache over other caching strategies. It’s also an excellent alternative to Varnish!

This is not a solution to consider sites with lots of logged in users as the cache is skipped entirely if the user is logged in.

Nonetheless, if you’re looking for the fastest static cache in town, look no further! Advantages include:

It’s very, very fast

A cached page will take only a few milliseconds to serve and bypasses PHP entirely. It’s like giving your server the weekend off.

No extra dependencies to install

There are no other NGINX modules or dependencies to install.

Your site will continue to serve cached pages if PHP borks

If PHP times out or returns a 500 response the cached page (where available) will be served.

No bloated plugins to install, configure and maintain

You don’t need to install a WordPress plugin (well, you do if you want to automatically purge the cache, but more on that later).

Over the years I’ve tended to use either W3 Total Cache or WP Super Cache. There are other alternatives now of course, but those are the 2 plugins I’ve had the most experience of.

Super Cache is pretty easy to configure and runs really fast on Apache with mod_rewrite.

W3TC is a massive plugin. There are tons of options and it’s a bit of a bitch to configure. It also adds a bunch of extra files which are a pain when you’re deploying stuff with Capistrano or some other deploy tool.

Ok, I’m sold, how do I do it?

Here an example for a simple NGINX config file

fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

server {
    listen   80;

    root /usr/share/nginx/html;
    index index.php index.html index.htm;

    server_name example.com;

    set $skip_cache 0;

    # POST requests and urls with a query string should always go to PHP
    if ($request_method = POST) {
        set $skip_cache 1;
    }
    if ($query_string != "") {
        set $skip_cache 1;
    }

    # Don't cache uris containing the following segments
    if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
        set $skip_cache 1;
    }

    # Don't use the cache for logged in users or recent commenters
    if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
        set $skip_cache 1;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }

    location ~ \.php$ {
        try_files $uri =404;

        include /etc/nginx/fastcgi_params;
.
        fastcgi_read_timeout 360s;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # Use the upstream for php5-fpm that we defined in nginx.conf
        fastcgi_pass php;

        fastcgi_index index.php;

        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
        fastcgi_cache WORDPRESS;
        fastcgi_cache_valid  60m;
    }
}

If you have a couple of sites on the same VPS or want to clean up your config a wee bit you can add the fast_cache_path stuff inside the html block of nginx.conf instead. You can also add a header to show cache hits and misses, which is really useful for debugging:

http {
    fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    fastcgi_cache_use_stale error timeout invalid_header http_500;
    fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
    add_header rt-Fastcgi-Cache $upstream_cache_status;

    ...
}

Obviously if you add this to your nginx.conf, don’t add it to your server config.

This should just work, but for the love of Kirk don’t try this on a production machine! Always run

sudo nginx -t

to make sure you haven’t (or I haven’t) borked your NGINX config somewhere.

We’ve used this on (for example) http://holmbergs.cc/ with very positive results. An uncached page takes around 500-750 milliseconds to generate in PHP. The cached page on the other hand takes around 20 milliseconds to serve (see for yourself!).

This is all very general of course. I haven’t run any proper benchmarks, but needless to say the cached page is WAY faster that the compiled page.

The biggest drawback with this approach is the lack of a built in method to flush the cache when a post is updated. For that reason I wrote a very simple little plugin (maybe an MU plugin?) that will flush the cache when a post is published. It’s at https://gist.github.com/richardsweeney/736465dddce8398357a9. It’s very simple for the moment and I may extend it to clear cached taxonomy pages and maybe even add a ‘flush cache’ button to the admin bar.

All in all I’m convinced that FastCGI caching is the best caching solution to date for WordPress (where static caching is an option) and I’m planning on using it in place of any existing WordPress plugin (where possible) from here on in.