Setting up PHP and WordPress on Nginx

Introduction

In this post we’ll install PHP5 and WordPress and serve it with Nginx. This post assumes we’ve already set up Nginx.

For our PHP projects, we want the same structure as our Python projects:

/var/www/
`- php-project/
  |- src/
  |- conf/
  |  |- php-fpm.conf
  |  `- nginx.conf
  `- logs/

Install PHP and FPM

We wanted to use uWSGI, but getting PHP running on it was too much trouble, so we settled on FPM.

$ apt-get install php5-fpm php5-cli

Let FPM know about our .conf files living in each of our project directories. Open up /etc/php5/fpm/php-fpm.conf and add the following line at the bottom:

include=/var/www/*/conf/php-fpm.conf

Sample PHP project

We can now try to create a new PHP project and see if it runs. Prepare our project directory:

$ cd /var/www
$ mkdir phptest
$ cd phptest
$ mkdir conf logs src

conf files

FPM provides a template for its conf files in /etc/php5/fpm/pool.d/. Let’s copy one into our conf/ folder and modify it for our project:

$ cp /etc/php5/fpm/pool.d/www.conf conf/php-fpm.conf

Set the conf’s pool name on the first line to the project’s name (default is [www]) and set the listen var to /tmp/$pool.sock ($pool holds the pool name which you set on the first line).

Here’s the full php-fpm.conf file with comments removed:

[phptest]
user = www-data
group = www-data
listen = /tmp/$pool.sock
access.log = /var/www/$pool/logs/php-fpm.log
listen.owner = www-data
listen.group = www-data
listen.mode = 0666


; kept at default values (unmodified) pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chdir = / And nginx.conf: server { listen 80; server_name localhost; root /var/www/phptest/src; access_log /var/www/phptest/logs/access.log; error_log /var/www/phptest/logs/error.log;

index index.php index.html index.htm;

try_files $uri $uri/ @rewrite;

location ~ .php$ { fastcgi_pass unix:///tmp/phptest.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }

location @rewrite { rewrite ^/(.*)$ /index.php/$1; }

#include global/restrictions.conf; }

index.php

We are ready to test our PHP project. Let’s create the iconic phpinfo() page.

$ echo "<?php echo phpinfo(); ?>" > src/index.php

Restart Nginx and FPM:

$ /etc/init.d/nginx restart
$ /etc/init.d/php-fpm restart

Navigate to http://localhost:80 to see your phpinfo.

WordPress

Alright, we have PHP installed and running. Let’s set up WordPress in the same structure as our test project.
WordPress needs a bit of configuration to set up, but it is well documented in their Codex entry about Nginx.

First, let’s install MySQL for PHP:

$ apt-get install mysql-server php5-mysql

Then create a database for WordPress:

$ mysql
mysql> CREATE DATABASE my_wordpress_db;
mysql> exit

Global conf files

WordPress’ Codex entry for Nginx provided some conf files to be included for WordPress to work with Nginx. We are using the “single blog” rule (slightly modified) for our setup.

Place the following files in /etc/nginx/global/:

restrictions.conf

# http://codex.wordpress.org/Nginx
# Global restrictions configuration file.
# Designed to be included in any server {} block.
 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; }

wordpress.conf

# 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 ~* .(js|css|png|jpg|jpeg|gif|ico)$ { expires 24h; log_not_found off; }

wordpress-w3-total-cache.conf

# BEGIN W3TC Browser Cache
gzip on;
gzip_types text/css application/x-javascript text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon;
location ~ .(css|js)$ {
 expires 31536000s;
 add_header Pragma "public";
 add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
 add_header X-Powered-By "W3 Total Cache/0.9.2.3";
}
location ~ .(html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml)$ {
 expires 180s;
 add_header Pragma "public";
 add_header Cache-Control "max-age=180, public, must-revalidate, proxy-revalidate";
 add_header X-Powered-By "W3 Total Cache/0.9.2.3";
}
location ~ .(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|swf|tar|tif|tiff|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$ {
 expires 31536000s;
 add_header Pragma "public";
 add_header Cache-Control "max-age=31536000, public, must-revalidate, proxy-revalidate";
 add_header X-Powered-By "W3 Total Cache/0.9.2.3";
}
# END W3TC Browser Cache
# BEGIN W3TC Skip 404 error handling by WordPress for static files
if (-f $request_filename) {
 break;
}
if (-d $request_filename) {
 break;
}
if ($request_uri ~ "(robots.txt|sitemap(_index|[0-9]+)?.xml(.gz)?)") {
 break;
}
if ($request_uri ~* .(css|js|html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml|asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|swf|tar|tif|tiff|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip)$) {
 return 404;
}
# END W3TC Skip 404 error handling by WordPress for static files

wordpress-wp-super-cache.conf

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


# Enable detection of the .gz extension for statically compressed content. # Comment out this line if static gzip support is not compiled into nginx. gzip_static on;

set $supercacheuri ""; set $supercachefile "$document_root/wp-content/cache/supercache/${http_host}${uri}index.html"; if (-e $supercachefile) { set $supercacheuri "/wp-content/cache/supercache/${http_host}${uri}index.html"; }

# If this is a POST request, pass the request onto WordPress. if ($request_method = POST) { set $supercacheuri ""; }

# If there is a query string, serve the uncached version. if ($query_string) { set $supercacheuri ""; }

# Logged in users and those who have posted a comment get the non-cached version. if ($http_cookie ~* comment_author_|wordpress_logged_in|wp-postpass_) { set $supercacheuri ""; }

# Mobile browsers get the non-cached version. # Wastes CPU cycles if there isn't a mobile browser WP theme for the site. if ($http_x_wap_profile) { set $supercacheuri ""; }

if ($http_profile) { set $supercacheuri ""; }

Configure WordPress

Alright, we’ve got our prerequisites ready, so let’s install and configure WordPress.

$ cd /var/www
$ mkdir my_blog
$ cd my_blog
$ mkdir src conf logs
$ curl -o latest.tar.gz http://wordpress.org/latest.tar.gz
$ tar -xzvf latest.tar.gz
$ mv wordpress src
$ cp src/wp-config-sample.php src/wp-config.php

Open up src/wp-config.php and change the following lines to match your MySQL info:

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'my_wordpress_db');
/** MySQL database username */
define('DB_USER', 'root');
/** MySQL database password */
define('DB_PASSWORD', 'my_root_password');
/** MySQL hostname */
define('DB_HOST', 'localhost');

Now all we need to do is set our project’s conf files.

conf/nginx.conf

server {
 listen 80;
 server_name localhost;
 root /var/www/my_blog/src/wordpress;
 access_log /var/www/my_blog/logs/access.log;
 error_log /var/www/my_blog/logs/error.log;


index index.php index.html index.htm;

try_files $uri $uri/ @rewrite;

include global/restrictions.conf; include global/wordpress.conf;

# 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;

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 unix:///tmp/my_blog.sock; }

location @rewrite { rewrite ^/(.*)$ /index.php/$1; } }

conf/php-fpm.conf

[my_blog]
user = www-data
group = www-data
listen = /tmp/$pool.sock
access.log = /var/www/$pool/logs/php-fpm.log
listen.owner = www-data
listen.group = www-data
listen.mode = 0666


; kept at default values (unmodified) pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chdir = /

Restart Nginx and FPM then navigate to http://localhost:80/wp-config/ to run WordPress’ configuration process and start blogging!