High-Performance WordPress with W3 Total Cache and Nginx

March 13, 2011 at 10:26pm.

This entry is about Programming

26 comments.

I recently set up a new server to host website for my clients. I took the opportunity to re-think how I have been serving sites and optimize the whole software stack for better performance. Rather than the usual LAMP (Linux/Apache/MySQL/PHP) stack, I decided to go with LEMP, switching Apache out for Nginx. Although most common PHP applications recommend Apache, they will actually run faster on Nginx. Usually all you just need to do is translate the .htaccess file rules into Nginx’s configuration file.

My first order of business once the new server was set up was to get WordPress running and to optimize its performance. The first few sites that would be going on the new hardware were using WordPress so although I will be going through a similar process for Expression Engine soon, WordPress was my test-case.

I always run the W3 Total Cache plugin with my WordPress installations. By storing copies of each rendered page to disk (as well as many other optimizations), W3TC dramatically decreases the load on the server under heavy traffic. This is particularly the case with the Nginx configuration I am about to show you, which lets most requests be handled by Nginx alone. Nginx was designed first and foremost as a reverse proxy, so it serves static files almost instantly and with minimal processor or memory usage. There are a number of sample configurations around the web for running WordPress with W3TC on Nginx, but none of them quite did it for me. For one thing, they all rely heavily on if statements, which are evil. So here is my take on it….

WordPress Settings

First, you need to install W3 Total Cache. This will add a “Performance” section at the bottom of the left-hand menu in your control panel. W3 Total Cache has many, many options and I am not going to explain them all here. To begin with, though, select the checkboxes to enable “Page Cache”, “Minify”, “Object Cache”, and “Database Cache”. The Page Cache method should be set to “Disk Enhanced” and all the others to “Disk” or “Opcode” if available.

Next, go to the “Page Cache” details page from the Performance menu and check off “Cache home page”, “Cache feeds”, and “Cache 404 pages”. The other two options in that section will be overridden by our Nginx configuration, so it doesn’t really matter what they are set to. Next, go to the “Minify” details page and add all the CSS and Javascript files that are used in your theme. This process is a bit fiddly—the upcoming version of W3TC will make it much easier—but the options are fairly self-explanatory. All the other settings should be fine with their defaults. The “Browser Cache” section, although very important for Apache setups, will also be overridden by what we are about to so, so you can ignore it.

Nginx Configuration

The important part of this post! You need to add the following configuration to your nginx.conf file (or to a separate file in /etc/nginx/sites-available/, which is the recommended way of doing things). I am assuming you already have Nginx running with PHP-FPM. If not, there are plenty of tutorials available around the web, depending on your Linux distro.

Caveats:

  • I had to comment out the PATH_INFO line in my fastcgi_params in order for images urls in minified CSS files to be correct. Doing so doesn’t seem to have had any negative effects.
  • Some of this code would be better off in your main http block, rather than the server block. I have arranged it this way for simplicity and clarity. Read more about how you should structure your configuration.
  • I’m not an Nginx configuration expert, so there may be ways to improve this code. Please share anything you see and I will update the post.

Comments:

Pulgafree gravatar

Pulgafree on March 20, 2011 at 1:00pm#1

I don’t get why didn’t you get any comments. Thank you so much for this “howto”. I implemented on all my WordPress client’s sites and I am more than happy seeing the results.

The Page Cache has made a BRUTAL improve on bandwidth and response times.

I have been using apache. But dropped in favor of nginx and PHP-FPM. Never looked back. WordPress sometimes hangs in loading. Some problem with the timing between PHP-FPM and MySQL. Haven’t figure it out yet. It happens randomly.

EliVZ gravatar

EliVZ on March 20, 2011 at 1:11pm#2

I’m glad it’s working well for you, Pulgafree! I haven’t seen that hanging issue with any of my sites yet, but if you figure out what’s causing it, will you leave another comment with the solution?

Pulgafree gravatar

Pulgafree on April 6, 2011 at 5:51pm#3

Hi. The problem was some misconfiguration on PHP-FPM, OpCode Cache and Mysql.

Used the atomic repo to update MySQL (I have CentOS 5.5), configured PHP this way and no more hangs:

PHP Version   5.2.17
Runtime Configuration:
Include Path
(include_path)  .:/usr/local/lib/php
Specifies a list of directories where the require(), include(), fopen(), file(), readfile() and file_get_contents() functions look for files.
Maximum Input Time
(max_input_time)  60 seconds
This sets the maximum time in seconds a script is allowed to parse input data, like POST, GET and file uploads.
Maximum Execution Time
(max_execution_time)  30 seconds
This sets the maximum time in seconds a script is allowed to run before it is terminated by the parser.
File Upload Settings:
Maximum File Size
(upload_max_filesize)  8 MB
The maximum size of an uploaded file.
Data handling:
Maximum Post Size
(post_max_size)  8 MB
Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize. If memory limit is enabled by your configure script, memory_limit also affects file uploading. Generally speaking, memory_limit should be larger than post_max_size.
Multibyte Function Overload
(mbstring.func_overload)  0
Overloads a set of single byte functions by the mbstring counterparts.

Pulgafree gravatar

Pulgafree on April 6, 2011 at 5:56pm#4

One question, I have this extension on php.ini:

extension=apc.so
apc.enabled = 1
apc.shm_segments = 1
apc.shm_size = 128M
apc.optimization = 0
apc.num_files_hint = 512
apc.user_entries_hint = 1024
apc.ttl = 300
apc.user_ttl = 300
apc.gc_ttl = 300
apc.cache_by_default = 1
apc.filters = “apc\.php$”
apc.slam_defense = 0
apc.use_request_time = 1
apc.mmap_file_mask = /tmp/apc-elefantina.XXXXXX
apc.file_update_protection = 2
apc.enable_cli = 0
apc.max_file_size = 2M
apc.stat = 0
apc.write_lock = 1
apc.report_autofilter = 0
apc.include_once_override = 0
apc.rfc1867 = 0
apc.rfc1867_prefix = “upload_”
apc.rfc1867_name = “APC_UPLOAD_PROGRESS”
apc.rfc1867_freq = 0
apc.localcache = 1
apc.localcache.size = 512
apc.coredump_unmap = 0
apc.stat_ctime = 0

For some reason, I can’t get it working right. It caches the configuration in w3 Total Cache. I mean, If I uncheck “Enable Minify”, and click on save, the next page loads and displays that Minify check is still on. I have to restart php-fpm to show the changes.

Should I reduce the apc ttl?

Ed gravatar

Ed on April 26, 2011 at 6:24pm#5

Hey Eli,

Great conf file!

I’ve been using it for the past few days in a new LEMP Test server and it’s working out great. So much more elegant than a lot of conf examples floating around.

Quick note - check this out - https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/

There is a concern that config blocks which pass to php using location ~* \.php$ can be compromised. A number of approaches to locking this down are mentioned in that post but the method I’ve used is to add

try_files $uri =404;

as the first line inside that config block.

Ed

EliVZ gravatar

EliVZ on April 26, 2011 at 6:39pm#6

Ed,

I may be wrong, but I believe that my try_files $uri /index.php; already solves this problem. If the file does not exist on the server, Nginx will pass the request off to WordPress, which then throws a 404.

(I actually have cgi.fix_pathinfo=0 in my php.ini also, which I should have mentioned in the post.)

Ed gravatar

Ed on April 27, 2011 at 2:18pm#7

Hey Eli,

I think you’re right - I re-read that post again.

You should submit your conf for inclusion to the nginx “Full Examples” webpage on the nginx website - http://wiki.nginx.org/Configuration

I think you’re conf is the best I’ve found so far - did you write it yourself?

Really top work!

EliVZ gravatar

EliVZ on April 27, 2011 at 3:29pm#8

That’s a good idea, and I will look into it. I definitely used ideas from many of the WP configurations I found around the web, but I also re-wrote most everything to conform to Nginx best-practices.

Thanks for your comments!

okone gravatar

okone on April 28, 2011 at 8:30am#9

This is a great post EliVZ, but I do have one problem to report, although its likely a fault with nginx rather than your configuration.

If you perform a search on wordpress and try to navigate to page two of the results, it actually directs you to page two of the blog. I have tried this on the few places I know to have the try_files rules in place and it seems to happen on all of them.

Of course, it might just be me going crazy but if anyone has any idea how to fix it let me know. I may end up asking in the nginx forums and see if they have any ideas.

Besides that, this is a really great post, so thank you!

EliVZ gravatar

EliVZ on April 28, 2011 at 11:28pm#10

Okone-

Hmm, good catch. It’s doing that on my sites too, although it happens even with caching turned off completely. I’m thinking there must be some incompatibility between Nginx and WordPress’s permalink handling. I just haven’t been able to track it down yet….

I will report back if I can figure out a fix, or if anyone else has seen this issue, please chime it.

okone gravatar

okone on April 29, 2011 at 8:33am#11

EliVZ

I created a thread on the nginx forums and we have an answer.

If you use:

try_files $uri $uri/ /index.php?q=$uri&$args;

instead of the recommended

try_files $uri $uri/ /index.php;

then search works fine. It is indeed a problem with wordpress internal rewriting.

EliVZ gravatar

EliVZ on April 29, 2011 at 12:33pm#12

Okone, you rock! I’ve updated my code and search works perfectly now. Thanks for figuring that out.

Robert Kingston gravatar

Robert Kingston on May 29, 2011 at 5:35am#13

Eli, YOU rock.

W3TC should have this conf inside its “Install” page.

Mads Madsen gravatar

Mads Madsen on June 4, 2011 at 9:24pm#14

I found a small problem with your config, the css and js files where not served as static content, it was still being served by PHP.
So changing:

rewrite ^/wp-content/w3tc/min/(.+\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last;

to

location ~ \.(css|js)$ {
  if (-f $request_filename) {
  break;
  }
  rewrite ^/wp-content/w3tc/min/(.+\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last;
}

Fixes it! :-)

Thank you for the nginx rules!

EliVZ gravatar

EliVZ on June 5, 2011 at 7:46pm#15

Mads-

Damn, you’re totally right. I’ve updated the configuration file. Thanks for catching that!

sanjeev gravatar

sanjeev on July 17, 2011 at 1:05pm#16

i request to all the website owners have this plugin..this plugin work great…

Sam gravatar

Sam on August 29, 2011 at 3:40am#17

Hello, how can I know nginx is actually using w3’s disk cache, cause I can’t see nginx accessing it through the access logs…

EliVZ gravatar

EliVZ on August 29, 2011 at 11:20am#18

Sam-

I’m sure there is a slicker way of checking where pages are being served from, but my quick-check is to edit the cached HTML file (in /wp-content/
w3tc/pgcache) with an extra comment at the bottom or whatever. Then check your website and see if the modification came through. Once you see that you are being served the cached copy, you can just click “Clear Page Cache” in W3TC to get a pure copy again.

Sam gravatar

Sam on August 29, 2011 at 2:45pm#19

Ah nice, thank you for pointing that out EliVZ!

SLT-A77 gravatar

SLT-A77 on November 17, 2011 at 5:42pm#20

hey there and thank you to your info – I have definitely picked up anything new from right here. I did on the other hand experience several technical points the use of this website, since I experienced to reload the site many instances previous to I could get it to load correctly. I were thinking about if your web host is OK?

NikolasTesla gravatar

NikolasTesla on January 14, 2012 at 1:04pm#21

I found the answer in <a >google</a>, remove the topic pls.

Erick gravatar

Erick on January 14, 2012 at 11:55pm#22

The “If” conditions in nginx setup are the best way to screw up nginx performance and get is close to apache. Remove the IFs and you’ll be benefitting, otherwise you might as well keep it simple and stick with apache.

Regeapave gravatar

Regeapave on March 16, 2012 at 2:13am#23

<a ><img>http://s008.radikal.ru/i306/1110/63/73cd3437daa9.jpg</img>             
<img>http://s010.radikal.ru/i313/1110/1a/27974488f9ee.jpg</img>             
<img>http://s017.radikal.ru/i419/1110/a2/10591b1f1009.jpg</img></a>           
           
Tegs: конструктор лего Power Miners Клешневой уловитель lego 8190 конструктор лего Atlantis Столкновение с крабоммонстром lego 8056   lego ninjago Ния спиннер лего из каталога 2011 года 2172.           
           
<u>купить конструкторы лего в екатеринбурге </u>             
строительно-игровые конструкторы лего купить              
конструкторы лего интернет мaгaзины          
<a >lego 5819 </a>

Yoav Aner gravatar

Yoav Aner on March 19, 2012 at 3:46am#24

Thanks for this guide. It got me started on configuring nginx with wordpress, which seems like a bit of a dark art. One thing I noticed however, perhaps it’s only in recent additions to W3TC, but W3TC generates its own nginx.conf file, which can/should be included within the master config? I’ve written a post with instructions on how to configure this on webfaction and benchmark between Apache, Nginx + PHP-FPM and Varnish.

EliVZ gravatar

EliVZ on March 19, 2012 at 10:41am#25

Yoav,

Yes, Frederick added the config file generation around the same time that I posted this script. I still prefer my own approach, which is much “lighter” and better follows Nginx best-practices, however they accomplish much the same result. I should probably update the post to explain the situation though….

Yoav Aner gravatar

Yoav Aner on March 20, 2012 at 6:14am#26

Thanks Eli,

Yes, I had a look at the auto-generated rules, and I can imagine they can be optimized (at least judging by the note about avoiding if statements). W3TC also adds its own optimization headers, which might or might not be desirable.

For people who are less comfortable with complex nginx rules, using the auto-generated rules is probably a good option.

p.s. I also noticed there are a couple of typos on your gist, for example there are two semicolons on one line, and if memory serves me nginx complains about a couple of other small things… it will be really useful if you post the most up-to-date version if you can.

Cheers
Yoav

Got something to say?