High Performance WordPress with Nginx and PHP-Fpm

This is a step by step guide on how to set up your own unmanaged server or vps to run as a WordPress Performance server environment.  Using this setup you will have the ability to host a large WordPress Multisite set up or numerous single installs.  This guide assumes you have a basic understanding of using SSH via the Mac Terminal app or Putty for Windows and requires an unmanaged dedicated server or VPS hosting account with either the 32 or 64 bit Debian kernal available.

In my last guide I touted a Nginx reverse proxy cache with WordPress and Apache as the ultimate WordPress performance stack but since then I have found that there is really no reason to use Apache unless you have a specific need for it.  Nginx has matured and works great with the WordPress rewrite engine and even W3 Total Cache now includes Nginx rewrite rules for page cache, minify and browser caching.

If you ever decide that managing you own web server isn’t for you then I highly recommend WP Engine WordPress Hosting WP Engine runs on Nginx and they handle all the caching for you.

Choosing a web host with Debian “squeeze” images available

Debian 6 aka “squeeze” is considered the grandfather of Linux distors and Debian is known for relatively strict adherence to the Unix and free software philosophies.  Debian is also distributed with access to repositories containing thousands of software packages ready for installation and use.

Most of the large managed hosting companies don’t give you the option of choosing your Linux distro so your going to need to find one that does.  The reason for this is that it’s much easier for a hosting company to provide support using cPanel or Parallels Virtuozzo VPS virtualization.  Virtuozzo allows them to squeeze many more VPSs on a box and control the resources and your limited to guess what – Cent OS.

Your going to need a provider that offers Xen virtualization.  Xen is a powerful open source virtualization platform that supports a wide range of guest operating systems including Linux – Debian, Ubuntu, Free BSD, Cent OS, Fedora and more.  When you set up your account have your provider load the Debian 6 aka “squeeze” image on your VPS or install it on your dedicated server.  If you have over 2GB memory go for the 64 bit version.

I use Softlayer (formally The Planet) for my dedicated server because of the state of the art data centers and major broadband backbone it’s connected to.  It also has a very powerful back end portal and allows you to connect to your box over a private VPN.  Linode, Slicehost, and VPS.net  are also very good choices.  I would stay away from Media Temple and Rackspace as I’ve had problems with both and they’ve had well documented  security vulnerabilities in the past.

Your provider will either provide you with an ip address and password to connect to your server for the first time or give you access to a control panel that you can use to load the image.

Some hosts include the network setup on the image others provide a java ssh console to connect.  If your only provided the java console you will need to set up the networking settings.

*Note: all entries in this article that require opening a file assume the nano text editor, some of you  might prefer vim or if your a real Linux Ninja Emacs (If you use Emacs you don’t need to read this article .)  If you prefer vim just substitue vi for nano.

If your provider pre-installed the image for you skip to Security.

nano /etc/network/interfaces

This is the file that contains the connection settings for each of the network cards on your server.  You will need your ip address and the gateway address which is to the router.  Your host should provide these for you.

auto lo
iface lo inet loopback
auto eth0 inet static
address xx.xx.xxx.xxx
gateway xx.xx.xxx.xxx

Ctrl-x will close your file and prompt you to save.

Next we will install the secure shell server

apt-get install openssh-server
/etc/init.d/ssh status
: sshd is running.

With networking and ssh setup you will be able to connect via ssh using Terminal or Putty

ssh root@xxx.xx.xx.xx

Enter your password at the prompt and accept the key fingerprint prompt which will add your new server or vps to your local known_hosts file.

Securing your server

The first step is securing your server.  To properly secure your server you need to disable password auth and use ssh keys to access your server.  Your key authorization will also be used for adding files via an SFTP program like Transmit.  We will also be updating all the packages and configuring the firewall using ufw.

apt-get update
apt-get upgrade-distro

Disable password authentication  in the sshd_config file.

nano /etc/ssh/sshd_config
Protocol 2
PasswordAuthentication no
UseDNS no

Open another terminal window to generate the ssh keys on your LOCAL machine.

mkdir ~/.ssh
cd ~/.ssh
ssh-keygen -t rsa

You will be prompted for a password to further secure your key and a name to save it under. Unless you all ready have existing keys just press enter to accept the default name of id_rsa / id_rsa.pub. The id_rsa.pub file is your public key and will need to be moved to the server.

scp ~/.ssh/id_rsa.pub root@xxx.xxx.xx.xx:

You will be prompted for your server password and your public key file will be in your root directory. Now will move it to the correct place and add it to the authorized_keys file. Close your 2nd terminal window and go back to the one you were using and connected to the server with.

mkdir ~/.ssh
mv ~/id_rsa.pub ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Now we will configure the firewall using ufw. UFW is an uncomplicated firewall that acts as an interface to control the servers iptables without the confusing syntax. It is shipped with Ubuntu but for Debian you will need to install: apt-get install ufw.

The following commands will open up the ports for ssh, web, smtp (php-mail), and mysql. You can add rules for any of the network services defined in the /etc/services file or roll your own custom rules.

ufw allow ssh
ufw allow www
ufw allow https
ufw allow mysql
ufw enable
ufw status

:To		Action	From
--		------	----
22		ALLOW	Anywhere
80		ALLOW	Anywhere
443		ALLOW	Anywhere
3306		ALLOW	Anywhere

Now we will restart ssh and test our connection. VERY IMPORTANT don’t disconnect your current ssh session! Test by opening another terminal window. If your on Mac you will get a keychain notice for your key password. Click the remember button so you won’t have to enter it every time.

Current terminal window

/etc/init.d/ssh reload

New Terminal window

ssh root@xxx.xxx.xx.xx

:Linux DebianLinux-001 2.6.32-5-amd64 #1 SMP Mon Mar 7 21:35:22 UTC 2011 x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Jul 12 21:40:45 2011 from 108-71-13-85.lightspeed.hstntx.sbcglobal.net

root@servername ~:

If you were successful give your self a pat on the back and close your original terminal window.

The Performance Stack – Nginx, PHP-FPM, MySQL

In the past, with Debian 5, it was very frustrating trying to stay up to date with the latest versions of PHP, MySQL and Nginx because the packages were always so outdated. Debian 6 shipped with up to date versions but if you want the latest performance packages your going to need the Dotdeb repositories.

Dotdeb is a repository containing packages to turn your Debian boxes into powerful, stable and up-to-date LAMP servers :

  • PHP 5.3
  • useful PHP extensions : APC, imagick, Pinba, xcache, Xdebug, XHprof…
  • MySQL 5.1 and its performant InnoDB plugin,
  • Maatkit…

Add the Dotdeb repos to /etc/apt/sources.list

deb http://packages.dotdeb.org stable all
deb-src http://packages.dotdeb.org stable all

Now fetch the appropriate GnuPG keys

wget http://www.dotdeb.org/dotdeb.gpg
cat dotdeb.gpg | apt-key add -

Run apt-get update and you should see your server connecting to the dot deb mirrors.

Note: Dotdob is a one man operation maintained by Guillaume Plessis, @W_a_s_t_e on Twitter. If you benefit or prosper from his packages consider donating

Install Nginx, PHP-FPM, MySQL, APC

apt-get install nginx-full php5-fpm php5 php5-mysql php5-apc php5-mysql php5-xsl php5-xmlrpc php5-sqlite php5-snmp php5-curl

You will be prompted to accept installs and apt-get will make sure all dependencies are met and install additional packages if needed. When you get to the MySQL install a semi-graphical interface will pop up. Make sure you choose a password for the root mysql user.

If you have made it this far go grab a beer or an espresso you deserve it, Systems Administrator.

The .conf files – configuring your web server

The default .conf files included in the Dotdeb packages will almost work right out of the box. You will need to make a few adjustments.

Nginx and PHP-FPM
Before you set up the Nginx.conf files you need to decide your folder structure and where the root of your websites will live. I like to use /srv/www/sitename/public for the web root and /srv/www/sitename/logs for the error and access logs. The files in your web root need to be owned by the same user as Nginx runs under. The Debian way is to run the server as www-data.

mkdir /srv/www/yoursitename /srv/www/yoursitename/public /srv/www/yoursitename/logs
chown www-data:www-data /srv/www/yoursitename/public/ -R

File: /etc/nginx/nginx.conf
Note: be wary of some of the copy and paste guides out there. The Slicehost and Linode tutorials are out of date and contain security vulnerabilities. If you don’t understand something question it and do your own research. The following files have been tested and are in use on real production servers.

user www-data;
worker_processes 4;
pid /var/run/nginx.pid;

events {
	worker_connections 1024;
	# multi_accept on;

http {
	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	server_tokens off;
	include mime.types;
	default_type  application/octet-stream;
	index index.php index.htm index.html redirect.php;

	gzip  on;
	gzip_vary on;
	gzip_proxied any;
	gzip_comp_level 6;
	gzip_buffers 16 8k;
	gzip_http_version 1.1;
	gzip_disable "MSIE [1-6].(?!.*SV1)";
	gzip_types text/plain text/css application/json application/x-javascript text/xml
                        application/xml application/xml+rss text/javascript;

	fastcgi_intercept_errors on;
	fastcgi_ignore_client_abort on;
	fastcgi_buffers 8 16k;
	fastcgi_buffer_size 32k;
	fastcgi_read_timeout 120;
	fastcgi_index  index.php;

	limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

	# Virtual Host Configs

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;  #Our individual site vhost server files will live here

If your familiar with the Apache Vhost concept you will be fine with Nginx. The Nginx server files we create act as individual site vhost.conf files and can either be in one large file or for better organization you can create a separate file for each site. The Debian way is to create 2 directories under /etc/nginx/, sites-available for the files and sites-enabled for sym links pointing to the file in sites-available. This allows you to turn off a site without having to delete it if you ever need it again.

touch /etc/nginx/sites-available/your_domain.com
ln -s /etc/nginx/sites-available/your_domain.com /etc/nginx/sites-enabled/your_domain.com
cd /etc/nginx/sites-enabled
ls -a
:drwxr-xr-x 2 root root 4096 Jun 26 02:04 .
drwxr-xr-x 5 root root 4096 Jun 26 01:50 ..
lrwxrwxrwx 1 root root   40 Jun 26 01:59 your_domain.com -> /etc/nginx/sites-available/your_domain.com

File: /etc/nginx/sites-available/your_domain.com
This is for a single WordPress install. We are also including the W3 Total Cache Nginx rewrite rules in a separate file w3-total.conf. You will need to run touch /etc/nginx/w3-total.conf and chown www-data:www-data /etc/nginx/w3-total.conf and list the path in the W3 Total Cache general settings so it can write to the file. *Note: Anytime you make a change to an Nginx server file you will need to restart.

server {
	listen 80;

	server_name your_domain.com www.your_domain.com;
	root /srv/www/domain/public;
	access_log /srv/www/domain/logs/access.log;
	error_log /srv/www/domain/logs/error.log;

        client_max_body_size 8M;
        client_body_buffer_size 128k;
        #The section below contains your WordPress rewrite rules
	location / {
                try_files $uri $uri/ /index.php?q=$uri&$args;
        location /search { limit_req zone=one burst=3 nodelay; rewrite ^ /index.php; }
	fastcgi_intercept_errors off;
	location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
		expires max;
		add_header Pragma public;
		add_header Cache-Control "public, must-revalidate, proxy-revalidate";

#sample 301 redirect
#	location /2011/06/26/permalink/ {
#		rewrite //2011/06/26/permalink/ http://example.com/2011/06/27/permalink_redirecting_to/ permanent;
#		}

#Send the php files to upstream to PHP-FPM
#This can also be added to separate file and added with an include
location ~ \.php {
        try_files $uri =404; #This line closes a big security hole
                             #see: http://forum.nginx.org/read.php?2,88845,page=3
        fastcgi_param  QUERY_STRING       $query_string;
        fastcgi_param  REQUEST_METHOD     $request_method;
        fastcgi_param  CONTENT_TYPE       $content_type;
        fastcgi_param  CONTENT_LENGTH     $content_length;

        fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param  REQUEST_URI        $request_uri;
        fastcgi_param  DOCUMENT_URI       $document_uri;
        fastcgi_param  DOCUMENT_ROOT      $document_root;
        fastcgi_param  SERVER_PROTOCOL    $server_protocol;

        fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param  SERVER_SOFTWARE    nginx;

        fastcgi_param  REMOTE_ADDR        $remote_addr;
        fastcgi_param  REMOTE_PORT        $remote_port;
        fastcgi_param  SERVER_ADDR        $server_addr;
        fastcgi_param  SERVER_PORT        $server_port;
        fastcgi_param  SERVER_NAME        $server_name;


#Once you have your w3-total.conf file ready un-comment out the line below
#include w3-total.conf;

You can now start your server and see if it works.

touch /srv/www/yoursite/public/index.php
echo '<!--?php echo "<br ?--></pre>
<h1>Nginx Baby - It WORKS!!!!</h1>
"; ?>' >> /srv/www/yoursite/public/index.php
/etc/init.d/nginx start

For more Nginx server file examples see the WordPress.org Nginx Codex

Open a browser and go to your domain or ip and if you did everything right you should see:

WordPress Multisite, alternative configuration and multiple non WordPress vhosts
If you want to host a multisite setup or add additional websites to your server or VPS you can include the WordPress Multisite rewrite rules in a separate file and simplify your per site vhosts.

File: /etc/nginx/nginx.conf

user www-data;
worker_processes  10;

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

events {
    worker_connections  1024;

http {
	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;
	client_max_body_size 13m;
	index              index.php index.html index.htm;

	upstream php {
	#	server;
		server unix:/tmp/php-fpm.sock;

	include sites-enabled/*;

This is a sample vhost for a subdomain WordPress Multisite that uses the Domain Mapping Plugin along with a few static sites that are also hosted on the server.

server {
	 listen 80;
	#Add a server_name entry for each mapped domain
	server_name star.main-multisite-domain.com *.main-multisite.com;
        server_name mapped-domain.com www.mapped-domain.com;
	server_name mapped-domain-2.com www.mapped-domain-2.com;
	server_name mapped-domain-3.com www.mapped-domain-3.com;

	root /srv/www/wordpress/public;
	access_log /srv/www/wordpress/logs/access.log;
	error_log /srv/www/wordpress/logs/error.log;
	index index.php index.html index.htm;

        #The Multisite rules are in the include below
	include global-wp-multi.conf;

#Begin Static Non WP Sites
#Repeat the vhosts below for each additional static site.  Make sure you make each one a root directory
server {

	server_name static-non-wp-domain.com www.static-non-wp-domain.com;
	root /srv/www/static-non-wp-domain/public;

        error_log /srv/www/static-non-wp-domain/logs/error.log;
	index index.php index.html index.htm;


File: /etc/nginx/global-wp-multi.conf
This is a default config file that will handle the rewrite rules for domain mapped subdomain multisites.

error_page 404 = @wordpress;
log_not_found off;

location / {
	try_files $uri $uri/ /index.php?$args;

rewrite /wp-admin$ $scheme://$host$uri/ permanent;

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
	expires 24h;
	log_not_found off;

rewrite ^/files/(.+) /wp-includes/ms-files.php?file=$1 last;

location ^~ /files/ {
	rewrite ^.*/files/(.+)$ /wp-includes/ms-files.php?file=$1 last;

include wp-total.conf;

# Rewrite multisite '.../wp-.*' and '.../*.php'.
if (!-e $request_filename) {
	rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last;
	rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last;

location @wordpress {
        fastcgi_pass php;
        fastcgi_param SCRIPT_FILENAME $document_root/index.php;
        fastcgi_param  QUERY_STRING       $query_string;
        fastcgi_param  REQUEST_METHOD     $request_method;
        fastcgi_param  CONTENT_TYPE       $content_type;
        fastcgi_param  CONTENT_LENGTH     $content_length;

        fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param  REQUEST_URI        $request_uri;
        fastcgi_param  DOCUMENT_URI       $document_uri;
        fastcgi_param  DOCUMENT_ROOT      $document_root;
        fastcgi_param  SERVER_PROTOCOL    $server_protocol;

        fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param  SERVER_SOFTWARE    nginx;

        fastcgi_param  REMOTE_ADDR        $remote_addr;
        fastcgi_param  REMOTE_PORT        $remote_port;
        fastcgi_param  SERVER_ADDR        $server_addr;
        fastcgi_param  SERVER_PORT        $server_port;
        fastcgi_param  SERVER_NAME        $server_name;

        fastcgi_param SCRIPT_NAME /index.php;

location ~ \.php$ {
	try_files $uri @wordpress;
	fastcgi_index index.php;
	fastcgi_pass php;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	fastcgi_param  QUERY_STRING       $query_string;
        fastcgi_param  REQUEST_METHOD     $request_method;
        fastcgi_param  CONTENT_TYPE       $content_type;
        fastcgi_param  CONTENT_LENGTH     $content_length;

        fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param  REQUEST_URI        $request_uri;
        fastcgi_param  DOCUMENT_URI       $document_uri;
        fastcgi_param  DOCUMENT_ROOT      $document_root;
        fastcgi_param  SERVER_PROTOCOL    $server_protocol;

        fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param  SERVER_SOFTWARE    nginx;

        fastcgi_param  REMOTE_ADDR        $remote_addr;
        fastcgi_param  REMOTE_PORT        $remote_port;
        fastcgi_param  SERVER_ADDR        $server_addr;
        fastcgi_param  SERVER_PORT        $server_port;
        fastcgi_param  SERVER_NAME        $server_name;


File: /etc/php5/conf.d/apc.ini

; configuration for php apc module
extension = apc.so
apc.enabled = 1
apc.shm_segments = 1
apc.shm_size = 512M
apc.optimization = 0
apc.num_files_hint = 2700
apc.user_entries_hint = 2700
apc.ttl = 7200
apc.user_ttl = 3600
apc.gc_ttl = 600
apc.cache_by_default = 1
apc.slam_defense = 1
apc.use_request_time = 1
apc.mmap_file_mask = /dev/zero
apc.file_update_protection = 2
apc.enable_cli = 0
apc.max_file_size = 2M
apc.stat = 1
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 = 1350
apc.coredump_unmap = 0
apc.stat_ctime = 0

Php.ini is located at /etc/php5/php.ini use the production defaults explained in the file.

File: /etc/my.cnf
The default is very long and well documented. Copy it to my.cnf.back and use this as a starting place (have at least 1GB Ram before using this file)

port		= 3306
socket		= /var/run/mysqld/mysqld.sock

socket		= /var/run/mysqld/mysqld.sock
nice		= 0

user		= mysql
pid-file	= /var/run/mysqld/mysqld.pid
socket		= /var/run/mysqld/mysqld.sock
port		= 3306
basedir		= /usr
datadir		= /var/lib/mysql
tmpdir		= /tmp
language	= /usr/share/mysql/english
key_buffer	= 16M
max_allowed_packet  = 16M
thread_stack	    = 192K
thread_cache_size   = 16
myisam-recover      = BACKUP
max_connections     = 100
table_cache         = 256
thread_concurrency  = 16
query_cache_limit   = 4M
query_cache_size    = 128M
general_log_file    = /var/log/mysql/mysql.log
general_log         = 1
log_slow_queries    = /var/log/mysql/mysql-slow.log
long_query_time = 2

expire_logs_days    = 10
max_binlog_size     = 100M

max_allowed_packet   = 16M

key_buffer	    = 16M

# * IMPORTANT: Additional settings that can override those from this file!
#   The files must end with '.cnf', otherwise they'll be ignored.
!includedir /etc/mysql/conf.d/

Whenever you make changes to a .conf file you need to restart the server to apply your changes.

/etc/init.d/mysql restart
/etc/init.d/nginx restart

WordPress via svn and setting up W3 Total Cache

The server is ready to install WordPress

Create your database

mysql  -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5340 to server version: 3.23.54

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> CREATE DATABASE databasename;
Query OK, 1 row affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON databasename.* TO "user_name"@"localhost"
    -> IDENTIFIED BY "password";
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.01 sec)

mysql> EXIT

Install WordPress via SVN

cd /srv/www/yourdomain/public
svn co http://core.svn.wordpress.org/tags/3.2.1 .

Add your database info to wp-config.php and install by visiting yourdomain.com/wp-admin/install.php

Use the Compatibility Check in W3 Total Cache to make sure everything is set up right. Yours will look similar to this one.

Set page cache and minify to disk enhanced. Object and database to APC. You can experiment with page cache via APC but with Nginx disk enhanced will most likely be faster.

Final thoughts

If your doing this for the first time set up a test install to get the hang of if before you rely on this for a production set up or client site. Using Debian and the apt-get repos it’s pretty easy to manage your own dedicated server or VPS and you can lose the cPanel training wheels. Not only will your page load times drop you will have a good feeling of accomplishment.

To maintain your packages run apt-get update and apt-get upgrade every once in a while and you can follow @dotdeb on Twitter to stay up to date with upgrades.

Feel free to leave a comment or question. I would love to hear how this set up works for you.

Resources and further reading

27 Comments (Add Yours)

  1. just checking one thing: the w3 total cache rewrite rules are only needed if using disk cache, right?

    • Yes you really only need the W3 rules for disc cache. They just add a finer level of control for the browser cache rules. I just updated the server file to show a full example of the W3 Rules using disk enhanced page cache and disk minify.

  2. Hi,

    Great post, thanks a lot for the information. From the title I got the impression, you were going into the details of multiple wp installs with one ngnix or via multiple xen installs.

    Do you have any best practices about this? Or even some up to date ressources?

    Thanks again for the great post.

    • Hi Costa,
      Thanks for the feedback. I updated the post to include configuration files for a WordPress multisite and also hosting multiple sites on the server.

  3. I’ve got a noob question about SSH keys. If I’ve installed the SSH key on the server it means that I can only login to it from my computer right? What if I don’t have my computer with me and have an emergency and need to SSH into the server?

    • Using SSH keys you will only be able to login if your computer contains a private key that matches a public key installed on the server. I prefer the added added security they provide but if you often need to access your server away from your computer then maybe a really strong password would be better for you.

      If your using password authentication you can change the ssh port to prevent a majority of the brute force attempts. Also some VPS and dedicated server hosts have a java console you can use to access your server without having to use your ssh keys.

  4. Just a note that “you are” is shortened to “you’re”, not “your”.

    Otherwise, thanks for the great write up!

  5. Hey Guys:

    Awesome tutorial — my first foray into nginx w/ wordpress.

    A couple things I got snagged on in the tutorial & the fixes:

    > When installing everything the first ‘php5-mysql’ should be ‘mysql-server’ instead since there is already a php5-mysql package a little later in the command

    > There’s a missing closing ‘}’ at the end of the file in the sites conf

    > Upon trying to install wordpress I got “Your PHP installation appears to be missing the MySQL extension which is required by WordPress.” Fixed this by editing the php.ini inside the php-fpm dir.
    >> I un-commented ‘extention=mysql.so’ as well as uncommenting ‘extention_dir = “./”‘ and changing ‘./’ to /usr/lib/php5/20090626+lfs and all was well.

    Thanks again guys! Loving nginx so far!

  6. Hi

    Great guide thank you, I have one question about file ownerships/permissions.

    Presumably everything in my site’s public folder needs to be owned by www-data and in the www-data group?

    If so, how can I then upload and/or edit files when logged in as a non-root user? Do I just need to add my user to the www-data group?

    Any advice much appreciated, I find this bit quite confusing!

  7. Any recommendations for creating init scripts to ensure all the services start automatically after a reboot?

  8. -location ~ \.php {
    +location ~ \.php$ {

    -location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
    +location ~* \.(ico|css|js|gif|jpe?g|png)$ {

    -rewrite ^.*/files/(.+)$ /wp-includes/ms-files.php?file=$1 last;
    +rewrite /files/(.+)$ /wp-includes/ms-files.php?file=$1 last;

  9. Hi,

    Thanks for your tutorial, I finally get my wordpress working on LEMP stack.

    However, if any of your reader need the easy way to install wordpress, you can use https://github.com/buchin/WordPress-On-Lemper
    Which is based on this tutorial.

    It assumes you’ve installed nginx, php-fpm, and mysql, and need multipe WordPress installed on multiple domains.

  10. For anyone else that finds this page, you may want to compare this install with http://www.lowendbox.com/blog/wordpress-cheap-vps-lowendscript/ as the lowendbox config gets rid of things that are not needed for installs on small vps accounts.


  11. Hi,
    Images in the post are not loading. Can you please rectify this? Thanks for the tutorial.

  12. Hi, using your guide to try and become a sys admin :). Anyway, got stuck on the security bit. Have a windows machine so I used puttygen to make keys, got it moved over to the right spot, changed the things in the sshd_config I thought I was supposed to but can’t get in (using pageant).. Any thoughts?

    • Woohoo, figured it out… The file that puttygen created had some extra stuff in it. I nano’d in in the terminal you so wisely told us to keep open. deleted the contents, then pasted the actual code from the puttygen window and BAM. It’s working :)

  13. Great tutorial. I have a couple of questions though, hope you can help:
    1. I plan to have multiple WP sites, one of which will be multisite with subdomains. Which nginx.conf should I use?

    2. Curious to know how much memory this tutorial assumes.

    Thanks so much!!

  14. Great piece. One correct. SoftLayer is NOT “formerly ThePlanet”. SoftLayer was a fantastic host long before they *acquired* ThePlanet.

  15. Also, to add: “there is no need for Apache” is not true for most production environments. When Nginx gets real rewrite functionality, the ability to have mod_security equivalents etc, *without* a gazillion “IF” conditions in the config file (which according to Nginx itself slows down the server considerably) that’s when Nginx can survive on its own. Until then, sure there are websites running only Nginx with PHP-FPM, but they have sacrificed a lot of production quality stuff.

  16. For putty users on windows, here’s a tutorial on how to configure auto login.

  17. Once you move to production, you can turn apc.stat = 1 to 0, for a bit of performance gain as APC won’t check every PHP file if it has changed recently

  18. In this tutorial failure, can not be successfully installed.

    Best to write a more detailed tutorial, thank you!

  19. Just found this tutorial and managed to get the WordPress install working just fine. Great tutorial, thanks.

    However, I have one small problem, namely WordPress 3.5.1 friendly URLs. The nginx re-write rules seems to prepend index.php onto all the permalink options, and if I have the 404 error option (the one where you mention the security hole), I just get 404 pages whenever I try to visit any content in the site.

    If I comment out that rule, they work, but they look like http://debiantest.com/index.php/sample-page/

    How do I get rid of the “index.php” from the rewrite rule and stop receiving 404 errors on all the pages/posts? I’ve tried various other sites for solutions, as this is apparently quite common, but even the recommended solution by the nginx developers doesn’t work.

    Thanks in advance