Home » CMSs » Using Nginx for WordPress

Using Nginx for WordPress

Wordpress NginxRecently we saw that a lot of people are switching from Apache to Nginx to power their WordPress site. Now in a previous article we talked about the advantages of Nginx, now we will give you a few tips on how to switch to Nginx from Apache.

Before we start we need to inform you that this has been tested on:

  • Arch Linux 64 bit (Nginx 0.8.54)
  • Mac OSX Server Leopard and OSX Server Snow Leopard via MacPorts (Nginx 0.8.54, php-fpm support requires modifying the PHP 5.3 ‘Portfile’)
  • Ubuntu Server 10.04 LTS (Nginx 0.8.54 with php-fpm)

Now WordPress configuration is mostly intertwined with Apache, so set ups and supports for Nginx is limited.

So, if you want WordPress to be powered by Nginx you need to configure it that way. For that are available the options: fastcgi’ or ‘php-fpm

We will show you how to configure with the second option ‘php-fpm’ because it’s easier. To make it easy for you to understand we have broken the configuration into five files:

Main (generic) startup file

This configuration is working if you are using Arch Linux: What we will show is equal to:

/etc/nginx/nginx.conf (or /etc/nginx/conf/nginx.conf

# Generic startup file.
user {user} {group};

#ususally equal to number of CPU’s you have. run command “grep processor /proc/cpuinfo | wc -l” to find it
worker_processes  2;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

# Keeps the logs free of messages about not being able to bind().
#daemon     off;

events {
worker_connections  1024;
}

http {
#    rewrite_log on;

include mime.types;
default_type       application/octet-stream;
access_log         /var/log/nginx/access.log;
sendfile           on;
#    tcp_nopush         on;
keepalive_timeout  3;
#    tcp_nodelay        on;
#    gzip               on;
#php max upload limit cannot be larger than this
client_max_body_size 13m;
index              index.php index.html index.htm;

# Upstream to abstract backend connection(s) for PHP.
upstream php {
#this should match value of “listen” directive in php-fpm pool
server unix:/tmp/php-fpm.sock;
#        server 127.0.0.1:9000;
}

include sites-enabled/*;
}

Now we know that what we are showing you is different than most Nginx configuration and is a bit difficult. But with a little effort we can do this.

Per Site configuration

# Redirect everything to the main site. We use a separate server statement and NOT an if statement – see http://wiki.nginx.org/IfIsEvil

server {
server_name  _;
rewrite ^ $scheme://mysite.com$request_uri redirect;
}

server {
server_name mysite.com;
root /var/www/mysite.com;

index index.php;

include global/restrictions.conf;

# Additional rules go here.

# Only include one of the files below.
include global/wordpress.conf;
#    include global/wordpress-ms-subdir.conf;
#    include global/wordpress-ms-subdomain.conf;
}

Global restrictions file

# Global restrictions configuration file.
# Designed to be included in any server {} block.</p>
location = /favicon.ico {
log_not_found off;
access_log off;
}

location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
deny all;
}

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}

General WordPress rules

This one is for installation of blogs:

# WordPress single blog rules.
# Designed to be included in any server {} block.

# This order might seem weird – this is attempted to match last if rules below fail.
# http://wiki.nginx.org/HttpCoreModule
location / {
try_files $uri $uri/ /index.php?$args;
}

# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;

# Directives to send expires headers and turn off 404 error logging.
location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
access_log off; log_not_found off; expires max;
}

# Uncomment one of the lines below for the appropriate caching plugin (if used).
#include global/wordpress-wp-super-cache.conf;
#include global/wordpress-w3-total-cache.conf;

# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
# This is a robust solution for path info security issue and works with “cgi.fix_pathinfo = 1” in /etc/php.ini (default)

include fastcgi.conf;
fastcgi_index index.php;
#    fastcgi_intercept_errors on;
fastcgi_pass php;
}

URL Rewrites / Permalinks

Now WordPress has a checker for Apache, but if you’ve decided to use nginx you need to use WordPress version higher from WordPress 3.7

You can use Nginx help plug in or enter this to a plug in:

add_filter( ‘got_rewrite’, ‘__return_true’ );

Also this code ‘/wp-content/mu-plugins/nginx.php’ will not be disabled.To have a filter ‘__return_true’ you will need to use a version of WordPress higher than 3.0.

WordPress Multisite Subdirectory rules

‘global/wordpress.conf’

# WordPress multisite subdirectory rules.
# Designed to be included in any server {} block.

# This order might seem weird – this is attempted to match last if rules below fail.
# http://wiki.nginx.org/HttpCoreModule
location / {
try_files $uri $uri/ /index.php?$args;
}

# Directives to send expires headers and turn off 404 error logging.
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires 24h;
log_not_found off;
}

location ~ ^/[_0-9a-zA-Z-]+/files/(.*)$ {
try_files /wp-content/blogs.dir/$blogid/files/$2 /wp-includes/ms-files.php?file=$2 ;
access_log off; log_not_found off; expires max;
}

#avoid php readfile()
location ^~ /blogs.dir {
internal;
alias /var/www/example.com/htdocs/wp-content/blogs.dir ;
access_log off; log_not_found off;      expires max;
}

# Uncomment one of the lines below for the appropriate caching plugin (if used).
#include global/wordpress-ms-subdir-wp-super-cache.conf;
#include global/wordpress-ms-subdir-w3-total-cache.conf;

# Rewrite multisite ‘…/wp-.*’ and ‘…/*.php’.
if (!-e $request_filename) {
rewrite /wp-admin$ $scheme://$host$uri/ permanent;
rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last;
rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last;
}

# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ \.php$ {
# Zero-day exploit defense.
# http://forum.nginx.org/read.php?2,88845,page=3
# Won’t work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
# Comment the ‘try_files’ line out if you set up php-fpm/php-fcgi on another machine.  And then cross your fingers that you won’t get hacked.
try_files $uri =404;

fastcgi_split_path_info ^(.+\.php)(/.+)$;
#NOTE: You should have “cgi.fix_pathinfo = 0;” in php.ini

include fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#    fastcgi_intercept_errors on;
fastcgi_pass php;
}

WP Super Cache Rules

# WP Super Cache rules.
# Designed to be included from a ‘wordpress-ms-…’ configuration file.

set $cache_uri $request_uri;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
set $cache_uri ‘null cache’;
}

if ($query_string != “”) {
set $cache_uri ‘null cache’;
}

# Don’t cache uris containing the following segments
if ($request_uri ~* “(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)”) {
set $cache_uri ‘null cache’;
}

# Don’t use the cache for logged in users or recent commenters
if ($http_cookie ~* “comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in”) {
set $cache_uri ‘null cache’;
}

# START MOBILE
# Mobile browsers section to server them non-cached version. COMMENTED by default as most modern wordpress themes including twenty-eleven are responsive. Uncomment config lines in this section if you want to use a plugin like WP-Touch
# if ($http_x_wap_profile) {
#        set $cache_uri ‘null cache’;
#}

#if ($http_profile) {
#        set $cache_uri ‘null cache’;
#}

#if ($http_user_agent ~* (2.0\ MMP|240×320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800)) {
#       set $cache_uri ‘null cache’;
#}

#if ($http_user_agent ~* (w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-)) {
#      set $cache_uri ‘null cache’;
#}
#END MOBILE

# Use cached or actual file if they exists, otherwise pass request to WordPress
location / {
try_files /wp-content/cache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php?$args ;
}

Now if you are using HTTPS WordPress super cache might use a different directory to make a difference between HTTP and HTTP.

location / {
try_files /wp-content/cache/supercache/$http_host/$scheme$cache_uri/index.html $uri $uri/ /index.php?$args ;
}

W3 Total Cache Rules

The third version of WordPress is using a different directory structure only for disk-based cache storage. That also depends on the configuration you have made.

#W3 TOTAL CACHE CHECK
set $cache_uri $request_uri;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
set $cache_uri ‘null cache’;
}
if ($query_string != “”) {
set $cache_uri ‘null cache’;
}

# Don’t cache uris containing the following segments
if ($request_uri ~* “(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)”) {
set $cache_uri ‘null cache’;
}

# Don’t use the cache for logged in users or recent commenters
if ($http_cookie ~* “comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in”) {
set $cache_uri ‘null cache’;
}
#ADD mobile rules from WP SUPER CACHE section above

#APPEND A CODE BLOCK FROM BELOW…

FOR Normal WordPress (without Multisite)

 # Use cached or actual file if they exists, otherwise pass request to WordPress
location / {
try_files /wp-content/w3tc/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?$args ;
}

FOR Multisite with subdirectories

if ($request_uri ~* “^/([_0-9a-zA-Z-]+)/.*” ){
set $blog $1;
}

set $blog “${blog}.”;

if ( $blog = “blog.” ){
set $blog “”;
}

# Use cached or actual file if they exists, otherwise pass request to WordPress
location / {
try_files /wp-content/w3tc-$blog$host/pgcache$cache_uri/_index.html $uri $uri/ /index.php?$args ;
}

FOR Multisite with Subdomains/Domain-mapping

location / {
try_files /wp-content/w3tc-$host/pgcache/$cache_uri/_index.html $uri $uri/ /index.php?$args;
}

It’s important to know the following:

Nginx can handle gzip & browser cache automatically so better leave that part to nginx.
W3 Total Cache Minify rules will work with above config without any issues.

Nginx fastcgi_cache

Nginx is doing catching on it’s own. But when you use Nginx built in fastcgi_cache you need to accumulate Nginx with fastcgi_cache_purge

Here is how that configuration is going to look.

Define a Nginx cache zone in http{…} block, outside server{…} block

#move next 3 lines to /etc/nginx/nginx.conf if you want to use fastcgi_cache across many sites
fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:500m inactive=60m;
fastcgi_cache_key “$scheme$request_method$host$request_uri”;
fastcgi_cache_use_stale error timeout invalid_header http_500;

or wordpress site config, in server{..} block add a cache check block as follow

#fastcgi_cache start
set $no_cache 0;

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

# Don’t cache uris containing the following segments
if ($request_uri ~* “(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)”) {
set $no_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 $no_cache 1;
}

Then make changes to PHP handling block

fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;

fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;

To become something like this

location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
# This is a robust solution for path info security issue and works with “cgi.fix_pathinfo = 1” in /etc/php.ini (default)

include fastcgi.conf;
fastcgi_index index.php;
#    fastcgi_intercept_errors on;
fastcgi_pass php;

fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;

fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;
}

Finally add a location for conditional purge

location ~ /purge(/.*) {
# Uncomment the following two lines to allow purge only from the webserver
#allow 127.0.0.1;
#deny all;

fastcgi_cache_purge WORDPRESS “$scheme$request_method$host$1”;
}

Better Performance for Static Files in Multisite

To have a better performance of Nginx configure

This:

map $http_host $blogid {
default               0;

example.com           1;
site1.example.com     2;
site1.com             2;
}

To this:

map $http_host $blogid {
default               0;

include /path/to/map.conf ;
}

And create:

ocation ~ ^/files/(.*)$ {
try_files /wp-content/blogs.dir/$blogid/$uri /wp-includes/ms-files.php?file=$1 ;
access_log off; log_not_found off; expires max;
}

Notes:

  • Nginx will map on its own every time a site is deleted or added.
  • With this method there won’t be any problems with accidental deletes
  • Perfect for larger networks

This configuration is used when the root of the site is a blog, if you put that blog in a different directory you will have to change the rules.

By using this:

set $wp_subdir “/blog”;

Warning
To test if your “uploads” directory is really protected, create a PHP file with some content (example: <?php phpinfo(); ?>), upload it to “uploads” directory (or one of its sub-directories), then try to access (execute) it from your browser.

Resource: https://codex.wordpress.org/Nginx

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*