Wordpress + Nginx + php-fpm on OVH VPS

I recently purchased a VPS Classic from OVH to migrate my blog. Worked good on my previous host service, but was a shared service that means sometimes the access time to a page was too long (very very long!).
A difference on a VPS (Virtual Private Server) comparing to a simple host service, is that you need to manage all the server stuffs: it is an empty box you need to configure to do what want. Considering the base VPS I bought has just 512Mb of RAM I tried to select and tune all the services installed.
All the following instructions are for the CentOS Linux distribution.

Install the webserver

First of all you need to add some external (not base) repositories:

rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm

rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

cat > /etc/yum.repos.d/nginx.repo << EOF
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
EOF

Now you are ready to install all required packages:

yum --enablerepo=remi,remi-php55 install nginx php-fpm php-common php-mysqlnd php-xml php-gd php-pdo mysql-server

If all worked well, you environment is ready for your wordpress blog

Configuration

Nginx (Engine X) web server does not include a module to use php as backend language, for this reason you should have an external "php server" to handle this kind of pages, for example php-fpm (PHP FastCGI Process Manager). In this setup we leave the configuration of php-fpm with all the defaults parameters (later we will tune it up...). That means it starts up with a TCP listener (127.0.0.1:9000) and we must configure nginx to send all http requests to it.
Create a file in /etc/nginx/conf.d named, for example, blog.conf. The following is my configuration file that is already optimized for the W3C TotalCache Plugin

server {
    listen 5.135.145.38:80;
    server_name blog.mornati.net;

    root /path/to/wordpress/file/blog;
    index               index.php index.html index.htm;

    access_log /var/log/nginx/blog.access.log;
    error_log /var/log/nginx/blog.error.log;

    # Use gzip compression
    # gzip_static       on;  # Uncomment if you compiled Nginx using --with-http_gzip_static_module
    gzip                on;
    gzip_disable        "msie6";
    gzip_vary           on;
    gzip_proxied        any;
    gzip_comp_level     5;
    gzip_buffers        16 8k;
    gzip_http_version   1.0;
    gzip_types          text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/png image/gif image/jpeg;

    # Rewrite minified CSS and JS files
    location ~* \.(css|js) {
        if (!-f $request_filename) {
            rewrite ^/wp-content/w3tc/min/(.+\.(css|js))$ /wp-content/w3tc/min/index.php?file=$1 last;
            # Use the following line instead for versions of W3TC pre-0.9.2.2
            # rewrite ^/wp-content/w3tc/min/([a-f0-9]+)\/(.+)\.(include(\-(footer|body))?(-nb)?)\.[0-9]+\.(css|js)$ /wp-content/w3tc/min/index.php?tt=$1&gg=$2&g=$3&t=$7 last;
        }
    }

    # Set a variable to work around the lack of nested conditionals
    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 'no cache';
    }
    if ($query_string != "") {
        set $cache_uri 'no 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|index\.php|wp\-comments\-popup\.php|wp\-links\-opml\.php|wp\-locations\.php)") {
        set $cache_uri "no 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 'no cache';
    }

    # 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?q=$uri&$args;
    }

    # Cache static files for as long as possible
    location ~* \.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
        try_files       $uri =404;
        expires         max;
        access_log      off;
    }

    # Deny access to hidden files
location ~* /\.ht {
        deny            all;
        access_log      off;
        log_not_found   off;
    }

    # Pass PHP scripts on to PHP-FPM
    location ~* \.php$ {
        try_files       $uri /index.php;
        fastcgi_index   index.php;
        fastcgi_pass    127.0.0.1:9000;
        include         fastcgi_params;
        fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
    }

}

As you can see in the latest location configuration, all requests to a php file will redirected to a fastcgi script (the php-fpm).
The important things to configure are:

  1. listen: address:port where your nginx webserver will listen for requests. Normally the base configuration listen 80 should work. On the OVH VPS you must specify the public ip address of your server to to prevent errors on nginx startup
  2. server_name: the variable to configure the virtual host of your webserver. That means all requests coming on your public ip address (the one configured in the listen variable) using the domain name specified in the server_name variable, will be handled by the current configuration. That also means you could have different website on the same nginx server; you just need to assign a different server name.
  3. root: the folder on your server containing files you want to serve through the webserver. For example all the wordpress files should be copied inside the folder configured here.

You are ready to extract wordpress (or any other php application) to the configured folder.

Start all services

Now you can test your configuration by starting up all the services:

service nginx start
service php-fpm start
service mysqld start

chkconfig nginx on
chkconfig php-fpm on
chkconfig mysqld on

Going to the configured domain name, should allow you the access to your wordpress blog (or to the wordpress setup page if need to configure a new blog).

Tuning

Depending on the traffic of your blog, you can try to tune it up to reduce the used resources.
For example, my blog has normally 200/300 visit per day from europe and USA, which is important to know the access time to the blog and "calculate" the simultaneous connections.
An important thing to understand is the meaning of "simultaneous": exactly in the same moment two (or more users) access to a website page (producing a request to the webserver, a php page compilation, ...). If you have a user that request a blog post and spends then 5 minutes reading it, in that 5 minutes you could have 2 or 3 other users accessing your blog. User are accessing simultaneously in the real life, but not for your application server: a reading user are not using resources from your server!

 

So... 200 users a day means you don't have many simultaneous connections (in theory, but is a thing you have to check), so you can configure the webserver (nginx and php-fpm) in consequence.

nginx tune
From nginx side, the important variables to start your tuning are workerprocesses and workerconnections: the first one configure how many nginx processes are created on your server (1 by default) and the second one indicates how many connections (clients) can handled by any process.
So you can calculate the number of clients allowed on your nginx with:
max clients = workerprocesses * workerconnections
On my server I leaved the default parameters for the nginx server, means 1 process with 1024 connections (file /etc/nginx/nginx.conf)

Why if I just say there are few concurrent users?
Another important thing to know is how a browser works with a web page.
When we ask a page to a webserver (for example a php page), it compile the php file (if needed) and send back to our browser the html version of a file (1 request to the webserver). Then, in the page, we normally have references to JavaScript files, Stylesheets files, images, fonts, ... So the browser, to complete the page we requested, execute other requests to the webserver: one for any static file. So a single user accessing a single page on a webserver, produces 10/20 requests (!!), or more, depending on your page. That means with 2 or 3 users we can easily reach hundreds connection to the server.

php-fpm tune
If you don't tune the default configuration, the php "web server" can handle hundreds connections without problems, BUT, it will use "lot" of ram: more than 200Mb.
Is not really an high value, I know, but when you have a virtual machine with 512mb of RAM, means the php-fpm uses half of your RAM.
The important configuration part is the one concerning the child processes.

; Choose how the process manager will control the number of child processes.
; Possible Values:
;   static  - a fixed number (pm.max_children) of child processes;
;   dynamic - the number of child processes are set dynamically based on the
;             following directives:
;             pm.max_children      - the maximum number of children that can
;                                    be alive at the same time.
;             pm.start_servers     - the number of children created on startup.
;             pm.min_spare_servers - the minimum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is less than this
;                                    number then some children will be created.
;             pm.max_spare_servers - the maximum number of children in 'idle'
;                                    state (waiting to process). If the number
;                                    of 'idle' processes is greater than this
;                                    number then some children will be killed.
; Note: This value is mandatory.

A child process could handles a single user requests. So, for example, 200 processes means 200 simultaneous users.
On my server, after some tests, I'm using this configuration:

pm = dynamic
pm.max_children = 4
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 200

That means I can have maximum 4 concurrent connections to the php part (for security you can increase the max to an higher value): a php thread normally answer to a user in less then a second (!).
When php-fpm service start up it creates 2 child processes, the others will be created only if required. You should find the good number for your child processes and put this number into start_servers variable: having required processes ready allow a faster response to the users (no time needed to create a new process).
With this configuration I can assure that php-fpm processes took only 70Mb of ram on my server (any new php-fpm process takes about 24Mb of ram more), and when I have moments with "many" concurrent users could go up to about 120Mb.

[mmornati@vps38203 ~]$ sudo python ps_mem.py 
 Private  +   Shared  =  RAM used    Program

  4.0 KiB +  27.5 KiB =  31.5 KiB    dbus-daemon
 36.0 KiB +  31.5 KiB =  67.5 KiB    atd
 24.0 KiB +  51.0 KiB =  75.0 KiB    mingetty (6)
 60.0 KiB +  24.0 KiB =  84.0 KiB    mdadm
224.0 KiB +  62.5 KiB = 286.5 KiB    crond
156.0 KiB + 147.0 KiB = 303.0 KiB    master
  4.0 KiB + 337.5 KiB = 341.5 KiB    mysqld_safe
220.0 KiB + 137.0 KiB = 357.0 KiB    qmgr
332.0 KiB +  35.0 KiB = 367.0 KiB    init
484.0 KiB +  81.5 KiB = 565.5 KiB    rsyslogd
728.0 KiB + 173.0 KiB = 901.0 KiB    nrsysmond (2)
660.0 KiB + 368.5 KiB =   1.0 MiB    bash
928.0 KiB + 431.5 KiB =   1.3 MiB    sudo
  1.4 MiB + 230.0 KiB =   1.7 MiB    nginx (2)
  1.2 MiB + 481.0 KiB =   1.7 MiB    pickup
940.0 KiB +   1.8 MiB =   2.7 MiB    sshd (3)
  3.3 MiB + 483.5 KiB =   3.7 MiB    fail2ban-server
  6.4 MiB + 236.5 KiB =   6.6 MiB    mysqld
  7.7 MiB + 175.0 KiB =   7.9 MiB    named
 74.4 MiB +   4.7 MiB =  79.1 MiB    php-fpm (3)
---------------------------------
                        109.1 MiB
=================================

To create this useful configuration I follow this guide. If !1 then 0 it's an incredible technical blog :)