Whether for speed, security or scalability, a WordPress site can be improved using NGINX.
View full webinar on-demand at: http://nginx.com/resources/webinars/taste-nginx-conf-wordpress-nginx-best-practices-easyengine/
5. Purpose
● Best solution for serving content to non-logged
in users
● Should be used instead full-page-cache from
WordPress plugin
6. Global Code Fragment
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 updating timeout
invalid_header http_500;
7. Site config
server {
location ~ .php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 60m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;}}
8. Cache conditions
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-
.*.php|index.php") {
set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-
9]+|wordpress_logged_in") {
set $skip_cache 1; }
9. Cache conditions
if ($request_uri ~* "/store/|/random/|/wp-admin/|...") {
set $skip_cache 1; }
if ($http_cookie ~* "comment_author||yummy_cookie|...") {
set $skip_cache 1; }
if ($remote_addr ~* "1.2.3.4") {
set $skip_cache 1; }
10. Caching search result pages
if ($query_string != "") {
set $skip_cache 1; }
# for http://example.com/?s=hello
if ($arg_s != "") {
set $skip_cache 0; }
11. Caching multiple versions of a page
# Cookie Example
fastcgi_cache_key "$scheme$request_method$host$request_uri$cookie_country";
# User-Agent Example
set $mobile no;
if ($http_user_agent ~* "iphone|android|blackberry|netfront") {
set $mobile yes;}
fastcgi_cache_key "$scheme$request_method$host$request_uri$mobile";
18. pagespeed global config
# Turning the module on and off
pagespeed on;
# Configuring PageSpeed Filters
pagespeed RewriteLevel PassThrough;
# Needs to exist and be writable by nginx. Use tmpfs for best performance.
pagespeed FileCachePath /var/ngx_pagespeed_cache;
24. What if load balancer fails?
● Configure backend server with NGINX in a
way that it can run as standalone server
● Use DNS-based failover to switch traffic from
load balancer to a backend server
31. Memcache/Redis - Distributed Cache
● For wordpress site is spread across multiple servers
● Only extra work is, WordPress/PHP side codes are
needed to “fill” cache
● For Memcache, NGINX has a core and few third party
modules
● For redis, NGINX has third party modules
33. What is EasyEngine?
● A script to setup and manage multiple
wordpress sites with NGINX web server
● Always up to date with best practices,
including most of the stuff we discussed so
far
34. Getting Started
#install easy-engine
wget -qO ee rt.cx/ee && sudo bash ee
#install nginx, php, mysql, postfix
ee stack install
#install WordPress on example.com
ee site create example.com --wpfc
35. WordPress Multisite Handling
#multisite with subdirectory
ee site create example.com --wpfc --wpsubdir
#multisite with subdomain
ee site create example.com --wpfc --wpsubdom
## Propsosal can be acceesed here - https://nginx.busyconf.com/proposals/53ff8857ea96fb95ce00007e?token=54e9k8ktl54t6wfg5vyk
Hello All!
I am Rahul Bansal, from rtCamp, a company based in India.
There are 2 parts to my presentation.
In first part, I will share WordPress-Nginx best practices we have gathered over last 4 years.
In second part, I will share a small project called “EasyEngine” which automates best practices that we are about to see, in first part!
WordPress, as we all know, is the best blogging/CMS platform in the world, used by 20% of the sites.
Nginx, again, is favorite web-server for top web-sites.
So when WordPress-Nginx comes together, it’s like two best-things coming together!
We are using best CMS with best web server!
We might expect everything to become best automatically.
But as different sites have different needs, we need to do make some changes.
We are going to see few of those changes/practices, which can help most WordPress sites.
Before we dive into nginx’s wonderland, let’s understand the golden rule.
Many times, you will come across things that can be done by nginx modules as well as wordpress plugins.
Always remember - “If you can do it in nginx, just do it in nginx!”
Most WordPress sites are used as publishing platform where content is served to non-logged in users
This fragment is common to all nginx sites
In above 3 lines only, a lot of magic is packed!
If you notice, fastcgi_cache_path is pointing to a location in /var/run - on debian this location uses tmpfs i.e memory-based storage.
The power of fastcgi_cache_key will be shown shortly.
The last line fastcgi_cache_use_stale tells nginx to use cached version, even if its outdated i.e. stale, if anything goes wrong with backend.
There are many things that can go wrong. You can control for which errors nginx should use stale version of page.
fastcgi_cache_path inactive value ensures, if a page is cached, nginx will show it for inactive duration, even if backend php-mysql is not responding.
Using fastcgi_cache_use_stale we can tell nginx to serve cached version of page, even if backend php-mysql is not responding, irrespective of “inactive” value.
This fragment is part of site config
We will explore fastcgi_cache_bypass and fastcgi_no_cache in next slide.
fastcgi_cache line specifies name of cache zone to be used for this block.
fastcgi_cache_valid is duration/timeout
There are certain conditions where we want nginx to skip caching
First “if” block prevents caching for HTTP POST requests.
Second “if” block prevents caching URL with query string
Third “if” block has list of URL patterns for which caching should not be done e.g. wordpress admin area
Fourth “if” block has list of Comment keys/patterns whose presence should skip cache.
The first cookie-key comment_author ensures, whenever somebody leaves comment on your blog, they see “Your comment is awaiting moderation” message with their comment content. But at the same time other users will continue to see cached pages as long as they don’t comment. The cached version without “Your comment is awaiting moderation” message.
We can specify “/store/” or “/random/” in request_uri pattern list.
If you are running a store, say powered by WooCommerce, you can skip caching for entire “store” section.
There is a wordpress-plugin which shows “random” post at “/random” URL. This can also be skipped from caching to make sure random remains random!
Similarly, during development, we can add yummy_cookie to the list and use a browser addon to set that cookie for your own browser. This way you can see completely uncached site from non-logged in user’s perspective.
See you don’t need to disable cache when making changes to the website!
Most wordpress sites search pages can be cached, even though pages have query string
You can also maintain cache for Google Analytics, Adword query string parameters. As well as any prosperity query arguments which doesn’t change outcome
The key is fastcgi_cache_key!
Just changed the key as per your needs.
For country specific cookie, you can simply use country-cookie as part of key.
If you set country-cookie to only 5 different values, then nginx will have 6 variations, 5 for 5 countries and 6th variation for when country value is blank!
Of course, if you set a default value for country, then 6th variation will not be created.
In case of User-Agent example, there are ton’s of them. So passing that value doesn’t make sense. Your wordpress theme has only 2 set of version - mobile and desktop.
So we need something like a switch. That is what we get in this example!
We use user-agent value to determine switch value and pass it to fastcgi_cookie_key
Most WordPress sites uses origin pull CDN.
Where a plugin is used to replace file
Remember that yellow sign in browser showing mixed content warning. There could be some plugin or theme assets which may hardcoded http:// prefix
Worry not!
This module can fix that for you!
This sub_filter can rewrite http-prefix asset URLs to https
The only problem is - not all wordpress themes and plugins play safe with CSS and JS combining.
So you need to test a lot. Remember to use one of method we discussed earlier to test things!
This is another nginx module which can be used in many ways.
A most common way is to get it involved when you are dealing with multiple servers. So let’s see that first!
When you have wordpress site or network running across multiple servers, a common way to use front-nginx as load balancer. Backend servers need to have nginx but we will see shortly why its good idea to have backend server on nginx as well!
In this config, all server will get equal weight i.e. equal number of requests.
ip_hash line ensures that a client/visitor always connects to the same backend server.
Another common mode is to have servers setup in failover mode.
Most likely primary server is more powerful and so expensive. A low-cost backup server is used to ensure only high availability.
In both cases, front-end nginx config will look like this.
X-Forwarded-Proto is to handle SSL forwarding
We can also add proxy_cache with similar conditions and magic like fastcgi_cache
That way load balancer front-end nginx handles most requests on its own, without passing any load to backend servers
Backend nginx config files should have a line like set_real_ip_from with front-end server IP address.
This way nginx can forward actual visitor IP address to WordPress/PHP sites.
set_real_ip_from requires nginx to be compiled with realip_module module
Load balancer may become single-point failure. Imagine how frustrated you will feel, when all backend servers are healthy but load balancer itself is down.
This is where we prefer to use DNS-based failover as last resort!
You can configure DNS-based failover to route traffic to a backend-server. This is why, earlier I recommended to have nginx ready on load balancer.
This is another nginx module which can be used in many ways.
A most common way is to get it involved when you are dealing with multiple servers. So let’s see that first!
The magic of fastcgi_cache works mostly for non-logged in users.
What about logged in users? Membership sites? Big stores?
Yep, WordPress or better say WooCommerce is bigger ecommerce platform than Magento in terms of number of stores.
When a request hits PHP, all that nginx magic seem to disappear. This is when HHVM - a PHP implementation by Facebook comes into picture but with its own set of problem.
HHVM doesn’t play nice with all WordPress themes and plugins. Worst, when it crash, it crashes hardway. It just doesn’t feel your error log with warnings but simply show “white screen of death”
Here come Nginx’s upstream module to the rescue!
As you can see in this config sample, we are running hhvm on port 8000 and slower but reliable PHP’s default FPM implementation on port 9000.
FPM is configured as backup to HHVM. So non-cached part of site will run fast with HHVM, but when HHVM crashes, PHP-FPM will take over.
In worst case, few seconds extra won’t hurt rather than showing “white screen of death” i.e. blank page!
There are many reasons for a WordPress site owner to choose SSL.
Till now most common reason was running e-commerce site.
But these days, SEO “experts” are driving sale of SSL certificates higher.
There was a time when SSL request used to cost much more. But now recommended way is to run entire site over SSL! The time saved by managing SSL related to config will be more costly than x% extra CPU cycle SSL request take!
Like other things, we can cache SSL sessions also.
This reduces CPU usage further!
As a wordpress site grows, it ends up having some broken links as structural changes to site takes place.
There are many WordPress plugins to handle redirections. When people ask me which one to use, I remind them of our golden rule!
If you can get list of single URL’s prefer that, even if you have many of them.
“location =” is fastest. I can say that with confidence because as a nginx user, I asked which question on nginx forum and Igor replied with this method.
I choose a toughest password for my wordpress admin login. But problem is random people try to log into WordPress sites all the time, by bruteforce attack method!
They are not going to succeed but their act generates load on precious php-mysql backend. And if they get greedy, then it can crash backend as well.
Remember we asked fastcgi to not cache out wp-login as well as HTTP POST requests.
So we need some way to limit these bruteforce attempts! That is what limit request module does for us.
10m is size of zone. 1MB can hold 16000 states. I think this means 16000 unique IP addresses. In case you have way too many sites or very high traffic sites, you may want to increase it to 20MB or 100MB.
1r/s means 1 request per second is allowed. You cannot specify fractions. If you want to slowdown further, means less requests per second try 30r/m which means 30 requests per min, effectively 1 request per 2 second (half request per second).
nodelay makes sure as soon as request limit exceeds, HTTP status code 503 (Service Unavailable) is returned by default.
In our case 444 will be returned.
Finally we are in second part. This will be short. Because everything is easy here.
Once you get a shell access to a VPS/dedicated server, preferably fresh one all you need is to run 3 commands.
The first command install easy-engine itself.
Second command, installs nginx with all required module including pagespeed, latest php and hhvm also, mysql and other dependencies
Third command will create a wordpress site for domain example.com with nginx’s fastcgi-cache.
The site will have nginx limit module’s bruteforce protection, increased PHP file upload limit, tweaked nginx, php, mysql config based on available RAM and much more.
It can also setup turn on nginx upstream block with hhvm with php-fpm fallback.
EE also installs nginx-helper.
WordPress Multisite setup, specially multisiste with subdirectory needs customized nginx config.
EasyEngine can handle that for you with its simple commands.
And by the way --wpfc is optional.