SaltyCrane Blog — Notes on JavaScript and web development

Using Nginx as a caching proxy with Wordpress+Apache

We have been evaluating caching reverse proxy servers at work. We looked at Nginx+memcached, Squid, and Varnish. Most recently, we found that Nginx version 0.7 has support for caching static files using the proxy_cache directive in the NginxHttpProxyModule. This allows us to use Nginx as a caching proxy without having to handle the complication (or flexibility depending on how you look at it) of setting and invalidating the cache as with the Nginx+memcached setup. Here are my notes for setting it up with an Apache+Wordpress backend.

Update 2010-01-05: Over a couple months, we switched to Nginx 0.8 and we made a few tweaks to our Nginx configuration. Here is our updated conf file: nginx_wordpress_100105.conf.

Install Nginx 0.7

The version of Nginx in Ubuntu is an older version so we used a PPA created by Jeff Waugh: https://launchpad.net/~jdub/+archive/ppa. (He also has a development PPA which contains Nginx 0.8.)

  • Add the following to /etc/apt/sources.list:
    deb http://ppa.launchpad.net/jdub/ppa/ubuntu hardy main 
    deb-src http://ppa.launchpad.net/jdub/ppa/ubuntu hardy main
  • Tell Ubuntu how to authenticate the PPA
    apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E9EEF4A1

    Alternively, if the keyserver is down, you can follow the instructions for copying the public key from http://forum.nginx.org/read.php?2,5177,11272.

  • Install Nginx from new PPA
    apt-get update
    apt-get install nginx
  • Check the version of Nginx
    nginx -V
    nginx version: nginx/0.7.62
    configure arguments: --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx.lock --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/body --http-proxy-temp-path=/var/lib/nginx/proxy --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --with-debug --with-http_stub_status_module --with-http_flv_module --with-http_ssl_module --with-http_dav_module --with-http_gzip_static_module --with-ipv6 --with-http_realip_module --with-http_xslt_module --with-http_image_filter_module --with-sha1=/usr/include/openssl

Configure Nginx cache logging

Within the http {} block, add:

    log_format cache '***$time_local '
                     '$upstream_cache_status '
                     'Cache-Control: $upstream_http_cache_control '
                     'Expires: $upstream_http_expires '
                     '"$request" ($status) '
                     '"$http_user_agent" ';
    access_log  /var/log/nginx/cache.log cache;

Nginx configuration for backend servers

Within the http {} block, add:

    include /etc/nginx/app-servers.include;

And /etc/nginx/app-servers.include looks like:

upstream backend {
        ip_hash;

	server 10.245.275.88:80;
	server 10.292.150.34:80;
}

Configure cache path/parameters

Within the http {} block, add:

    proxy_cache_path /var/www/nginx_cache levels=1:2
                     keys_zone=one:10m;
                     inactive=7d max_size=200m;
    proxy_temp_path /var/www/nginx_temp;

More proxy cache configuration

We added the username from the wordpress_logged_in_* cookie as part of the cache key so that different logged in users will get the appropriate page from the cache. However, our Wordpress configuration sends HTTP headers disabling the cache when a user is logged in so this is actually not used. But it does not hurt to include this, in case we change our Wordpress configuration in the future.

Within the server {} block, add:

        location / {
            # capture cookie for use in cache key
            if ($http_cookie ~* "wordpress_logged_in_[^=]*=([^%]+)%7C") {
                set $my_cookie $1;
            }

            proxy_pass http://backend;
            proxy_cache one;
            proxy_cache_key $scheme$proxy_host$uri$is_args$args$my_cookie;
            proxy_cache_valid  200 302 304 10m;
            proxy_cache_valid  301 1h;
            proxy_cache_valid  any 1m;
        }

Configure locations that shouldn't be cached

If WordPress sends the appropriate HTTP Cache-Control headers, this step is not necessary. But we have added it to be on the safe side. Within the server {} block, add:

        location /wp-admin { proxy_pass http://backend; }
        location /wp-login.php { proxy_pass http://backend; }

Restart Nginx

The Nginx reverse proxy cache should work without modification to the Apache configuration. In our case, we had to disable WP Super Cache because we had been using that previously.

/etc/init.d/nginx restart

View the log

Check the /var/log/nginx/cache.log to see if everything is working correctly. The log should diplay HIT, MISS, and EXPIRED appropriately. If the log shows only misses, check the Cache-Control and Expires HTTP headers that are sent from Apache+Wordpress.

Example Apache/Wordpress configuration that disabled the Nginx cache

Part of the WP Super Cache configuration included the following in the .htaccess file. It had to be removed for Nginx cache the pages. (In particular, the must-revalidate part had to be removed.)

     Header set Cache-Control 'max-age=300, must-revalidate'

Comments


#1 Vitol commented on :

Hi,

I have googled a couple of days to find any info about configuring nginx+apache+wordpress with proxy cahce. It seems your post is the only one! Thank you for this work!

I have a gap in understanding how the nginx's proxy_cache actually works: does it cache only statics or apache genrated html?

Thank you in advance!


#2 Eliot commented on :

Hi Vitol, I'm glad you found the post-- hopefully it will be helpful to you. The Nginx forum also has a lot of information. Do a search for "proxy_cache" there.

To answer your question, Nginx can cache both static media files and apache generated html.


#3 Oren commented on :

Hi Eliot,

How do you deal with cache purging? For example, if some user who is not logged in posted a comment, you need to invalidate the cached page. Or else, that user will get a cached version of a page without his own comment.

Oren


#4 Eliot commented on :

Hi Oren,
This is an issue that we faced. This Nginx module doesn't support purging the cache so we just have to wait the short time (10 minutes in our case) for the cached page to expire.

Regarding the comment pages, we actually added the comment author's name and email from the Wordpress cookie as part of the cache key so each person sees their own page. I added a link to our updated conf file at the top of the post.

Eliot


#5 Maciej commented on :

Cool, this helped me a lot. My WP is running on nginx and admin works, thanks!


#8 Eliot commented on :

Pranav: I'm no longer using WordPress, but thanks for the tip. Your articles look really good.


#9 Hpatoio commented on :

For those who are reading this article.

You might be interested in this plugin http://wordpress.org/extend/plugins/nginx-manager/

It keep the NGINX cache always fresh by purging pages each time a new post/comment is published/modified.

Enjoy

-- Simone


#10 Devko commented on :

Thx, got the cache running now!


#11 The unlock guy commented on :

I had a lot of work to do to make the cache running, but I finally done it thanks to you.