If you are running WordPress on a VPS or dedicated server, you may want to install the nginx HTTP server to act as a proxy cache for WordPress.
This is a guide for building nginx from source so that 2 modules can be added. The first module allows purging content from the proxy cache and the second module allows limiting the max concurrent requests to apache.
This guide assumes you are running:
- Ubuntu Server 10.04
- Apache HTTP Server Version 2.0
1. Configure ubuntu with needed development packages not installed by default and update/upgrade ubuntu
apt-get update apt-get upgrade apt-get install nginx build-essential libpcre3-dev libssl-dev zlib1g-dev cd /usr/src/
2. Get latest nginx
wget http://www.nginx.org/download/nginx-0.8.52.tar.gz tar -xvf nginx-*.tar.gz rm nginx-*.tar.gz mv nginx-* nginx-core
3. Get ngx_cache_purge module – adds ability to purge content from proxy cache
wget http://labs.frickle.com/files/ngx_cache_purge-1.2.tar.gz tar -xvf ngx_cache_purge-*.tar.gz rm ngx_cache_purge-*.tar.gz mv ngx_cache_purge-* ngx_cache_purge
4. Get nginx-ey-balancer module – adds a request queue that allows limiting of concurrent requests to apache
wget https://github.com/ezmobius/nginx-ey-balancer/tarball/master -O nginx-ey-balancer.tar.gz tar -xvf nginx-ey-balancer.tar.gz rm nginx-ey-balancer.tar.gz mv ry-nginx-ey-balancer-* nginx-ey-balancer
5. Patch and configure nginx
cd nginx-core patch -p0 < ../nginx-ey-balancer/patches/nginx-0.8.32.patch ./configure --sbin-path=/usr/sbin --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 --add-module=/usr/src/ngx_cache_purge --add-module=/usr/src/nginx-ey-balancer
6. Build nginx
/etc/init.d/nginx stop make && make install
7. Configure nginx
vim /etc/nginx/nginx.conf # main nginx config user www-data; worker_processes 4; #based on number of CPU cores error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; events { worker_connections 1024; } http { server_tokens off; include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; client_body_temp_path /var/lib/nginx/body 1 2; gzip_buffers 32 8k; sendfile on; keepalive_timeout 65; tcp_nodelay on; gzip on; gzip_types text/css text/xml application/x-javascript application/atom+xml text/plain application/rtf; include /etc/nginx/sites-enabled/*; }
8. Configure nginx server replacing listen with your public ip
vim /etc/nginx/sites-enabled/default # server setup proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=staticfilecache:180m max_size=500m; proxy_read_timeout 120; proxy_send_timeout 120; proxy_cache_key "$scheme://$host$request_uri"; upstream apache { server 127.0.0.1:8080 weight=1 fail_timeout=120s; max_connections 3; } server { #Listen to your public IP listen 192.168.1.98:80; log_format custom '$host $uri $remote_addr [$time_local] ' '$status $bytes_sent [$request]'; access_log /var/log/nginx/access.log custom; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Accept-Encoding ""; proxy_cache_valid 200 20m; location / { # If logged in, don't cache. if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) { set $do_not_cache 1; } proxy_no_cache $do_not_cache; proxy_cache_bypass $do_not_cache; proxy_cache staticfilecache; proxy_pass http://apache; proxy_cache_valid 200 20m; } location ~ /purge(/.*) { proxy_cache_purge staticfilecache "$scheme://$host$1"; } location ~* wp-.*.php|wp-admin { proxy_pass http://apache; } location ~* .(jpg|png|gif|jpeg|css|js|mp3|wav|swf|mov|doc|pdf|xls|ppt|docx|pptx|xlsx|ico)$ { proxy_cache staticfilecache; proxy_pass http://apache; proxy_cache_valid 200 120m; expires 15552000; } location ~* /[^/]+/(feed|.xml)/? { if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) { set $do_not_cache 1; } proxy_no_cache $do_not_cache; proxy_cache_bypass $do_not_cache; proxy_cache staticfilecache; proxy_pass http://apache; proxy_cache_valid 200 45m; expires 3600; } location = /50x.html { root /var/www/nginx-default; } # No access to .htaccess files. location ~ /.ht { deny all; } }
9. Configure apache to listen on port 8080, also if you have expires rules set up in apache you will need to disable them for the proxy cache to work
vim /etc/apache2/ports.conf vim /etc/apache2/sites-enabled/000-default
10. Restart nginx and apache
/etc/init.d/nginx restart /etc/init.d/apache2 restart
11. Test
- You should now be able to go to http://www.example.com/ and your page will be served and saved in the proxy cache
- After visiting a page at http://www.example.com/blog/ you can clear the proxy cache by going to http://www.example.com/purge/blog/
- If you are logged in to WordPress or have left a comment you will always receive a non-cached page from apache
12. You installed nginx via apt-get to take advantage of preconfigured directories, init scripts, etc. Because of this you may want to prevent apt-get from upgrading nginx in the future.
vim /etc/apt/preferences.d/nginx Package: nginx Pin: version 0.8.52* Pin-Priority: 1000
I hope this guide has been useful, if you have any questions or corrections please leave a comment.
The following were most valuable in providing the basis for the above guide.
- Nginx HTTP Server
- http://blogs.law.harvard.edu/djcp/2010/01/nginx-as-a-front-end-proxy-cache-for-wordpress/
- http://wordpress.org/extend/plugins/nginx-proxy-cache-integrator/installation/
- http://www.myatus.co.uk/2010/06/28/a-simplified-nginx-apache-combo-with-wordpress-support/
- http://mcnearney.net/blog/2010/2/28/compiling-nginx-cache-purging-support/
hi john, i read your article with to much attention because i have a wp 3.0.1 mulitisite multidomain installed in a shared hosting and i would upgrate it to a vps. so your notes are very usefull.
one question i would ask is what’s are a typical vps requirement for this type of architecture, how many mb ram it’s needed?
best regards, pescadito
LikeLike
I’m glad you found this useful.
I have used this configuration on a VPS running as little as 512MB of ram. That is the beauty of nginx, it is very lightweight and uses minimal ram when compared to apache.
LikeLike
I like to lock my wp-admin and wp-login down with extra authentication. If you go that route changing
location ~* wp-.*.php|wp-admin {
tolocation ~* wp-login.php|wp-admin {
fixes this up nicely.I’ve also got one site that’s completely SSL and the links returned from Apache+WP are all pointing to the unencrypted site. Any thoughts on fixing that up?
Great article, thanks for writing this up.
LikeLike
If you use this combination you might be interested in this plugin http://wordpress.org/extend/plugins/nginx-manager/
It gives you more control over NGINX cache and purge page when news post are published on WP.
LikeLike
Thanks.
I also created a plugin http://wordpress.org/extend/plugins/nginx-proxy-cache-purge/ that purges the cache upon publishing a post.
LikeLike
First, thanks – this totally rocks.
Second, do you have any suggestions for how I can verify that pages are being served from the cache? I installed your plugin and everything, but it seems like when I watch the apache access log I still see it hitting apache even after the initial load of a page. If the caching is working, shouldn’t it only serve from nginx after the initial hit?
LikeLike
A couple of ideas. Make sure that you are not logged into WordPress and that you don’t have a comment cookie set in your browser. Pages will not be served from the cache if either of those scenarios exist. Maybe you can try viewing your site in a different browser and then look at your apache logs.
LikeLike
If you install my plugin http://wordpress.org/extend/plugins/nginx-manager/ you will find 2 extra HTTP headers in your response that tell you if the page has been served from the cache (and when will expire) or not.
LikeLike
Do you know if there are different instructions for the latest stable release of nginx (1.0.2)? I wasn’t able to get it to work with that, but the instructions work fine for 0.8.54.
Really appreciate the instructions and assistance!
LikeLike
I haven’t tried yet, but you might need to use the updated version 1.3 of the ngx_cache_purge module. Let us know how it goes.
LikeLike
Hi John — I just got around to trying, but wanted to let you know that nginx 1.0.4 did work with the updated version of the module. Thanks for pointing me in the right direction!
LikeLike
Would the install generally be the same on CentOS at MediaTemple on their VPS service? I’m not a programmer or sys admin and am learning. Let me know. Thanks.
LikeLike
Should be about the same on CentOS (Red Hat) with the exception that the packages needed to install nginx may be different. I’m not sure which development packages are installed by default. Also, the directory path for the config files may be different. In any case, I hope that this post points you in the right direction.
LikeLike
Works like a charm. Just did a copy/paste of your lines and that was it. Makes much more sense that WP-SuperCache plugin.
LikeLike
This is a very neat and useful plugin when running WP under a LEMP stack, where I put it in mu-plugins so people can’t mess with it! The plugin works just as well with fastcgi caching of course so Apache can be ditched completely.
But I noticed that if a comment is made which needs to be approved, the approval is not immediately reflected in the posts page (ie the comment count) until a new post or an already-approved commenter adds something which will cause a cache refresh.
The answer is to hook into the action ‘transition_comment_status’ a one-line function thus:
function wp_transition_comment_in_cache($new_status,$old_status,$comment){
#extract post ID from comment object iff the status is changed then purge the cache
if($new_status != $old_status){
wp_select_cache($comment->comment_post_ID);
}
}
add_action('transition_comment_status','wp_transition_comment_in_cache',10,3);
I don’t know whether there is a better way of doing this – I don’t think it is a case of monitoring cookies and changing the regex in the nginx configuration.
HTH
LikeLike