WordPress: a recipe for switching from Apache to Nginx.

Posted by – Monday 2016-05-23

This blog runs on a bunch of software items, among them an instance of the WordPress content management system and a web server. For years I have trusted on the Apache HTTP server, despite I never liked it. Recently I switched to Nginx, a web server that is gaining a lot of traction, particularly among the busiest web sites [1, 2].

Why did I do this switch? For some reason unknown to me, some weeks ago Apache’s appetite for RAM memory went through the roof. None of the solutions I tested worked, including an update from version 2.2 to 2.4. Thus, I had a blog that was unusable: almost every request raised a HTTP 500 error. The following data provides some context: this blog is a low traffic one – it serves an average of 70 pages per day – and it is hosted on an inexpensive VPS that has 512 MB of RAM memory.

The solution I took was switching to Nginx, and it worked.

LEMP: Linux + Nginx + MySQL + PHP

This post is a quick-and-dirty recipe for switching an existing WordPress blog from the Apache web server to Nginx in a machine running a Debian 7 operating system. There is no original research: the instructions given here are taken from several web pages about Nginx, PHP and WordPress and put together here; and it is likely that there is room for improvement.

1. The arena: Nginx + PHP FastCGI Process Manager.

The first step is installing Nginx.

$ sudo apt-get install nginx

Unlike Apache, Nginx cannot handle directly dynamic generated contents. For instance, while Apache can process directly PHP pages by using its mod_php module, Nginx has to forward these kind of requests to a backend application server. Our setup uses PHP FastCGI Process Manager [3] (PHP-FPM for short) as backend.

The second step is installing PHP-FPM.

$ sudo apt-get install php5-fpm

After that, we have to configure Nginx and PHP-FPM in order to make them work together, that is, getting that the web server forwards the PHP requests to the PHP-FPM backend.

2. The Nginx side.

We will leave the main Nginx configuration file (/etc/nginx/nginx.conf) unmodified. In the Nginx sites-available directory (/etc/nginx/sites-available/) we will find the file default, which we will copy in the same directory with a name like my-wp. The file /etc/nginx/sites-available/my-wp will look like this:

server {
   listen 80 default_server;
   listen [::]:80 default_server;

   # SSL configuration
   #
   # listen 443 ssl default_server;
   # listen [::]:443 ssl default_server;
   #
   # Self signed certs generated by the ssl-cert package
   # Don't use them in a production server!
   #
   # include snippets/snakeoil.conf;

   root /var/www/my-wp/;

   # Add index.php to the list if you are using PHP
   index index.html index.htm index.php; 

   server_name _;

   location / {
      # First attempt to serve request as file, then
      # as directory, then fall back to displaying a 404.
      index index.html index.htm index.php;
      try_files $uri $uri/ = 404;
   }

   location ~ [^/]\.php(/|$) {
      fastcgi_split_path_info ^(.+?\.php)(/.*)$;
      if (!-f $document_root$fastcgi_script_name) {
         return 404;
      }

      fastcgi_pass unix:/var/run/php5-fpm.sock;
      fastcgi_index index.php;

      fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
      include fastcgi_params;
   }
   if (!-e $request_filename){
      rewrite ^(.*)$ /index.php?url=$1 break;
   }

   # deny access to .htaccess files, if Apache's document root
   # concurs with nginx's one
   #
   #location ~ /\.ht {
   #  deny all;
   #}
}

In this example we are assuming that the our blog uses the port 80 (lines #2 and #3). Should this not be the case, port number value must be changed accordingly. On the other hand, the root directory (line 15) must be changed to the one where the existing WordPress blog is.

The FastCGI connection is done using an UNIX socket (line #34); an alternative – not discussed here – is using a TCP port [4].

The site my-wp must be enabled. This is done by creating a symbolic link in the directory /etc/nginx/sites-enabled/.

$ sudo ln -s /etc/nginx/sites-available/my-wp /etc/nginx/sites-enabled/my-wp

3. The PHP-FPM side.

PHP-FPM allows running different websites under dedicated pools. A pool is a set of processes that share a configuration, like user, group or listening port. The main benefit of pools is that they can be used to provide isolation, thus improving security.

When the package php-fpm was installed, a default pool was created. The name of this pool is www, and it will be used by the WordPress blog.

The configuration file of this pool is /etc/php5/fpm/pool.d/www.conf. Given that this file is large – over 425 lines -, we will just show and comment the most relevant sections.

; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
;       will be used.
user = www-data
group = www-data

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific IPv4 address on
;                            a specific port;
;   '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all IPv4 addresses on a
;                            specific port;
;   '[::]:port'            - to listen on a TCP socket to all addresses
;                            (IPv6 and IPv4-mapped) on a specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = /var/run/php5-fpm.sock

...

; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions. 
; Default Values: user and group are set as the running user
;                 mode is set to 0660
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

...

The user and group (lines #4 and #5) of the pool must have permissions to read and write on the root directory of the WordPress blog. The parameter listen (line #19) is also important: it must match the parameter fastcgi_pass of the website Nginx configuration file.

The number of created processes should not be overlooked, particularly on servers with little amount of RAM memory [5].

; 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. With this process management, there will be
;             always at least 1 children.
;             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.
;  ondemand - no children are created at startup. Children will be forked when
;             new requests will connect. The following parameter are used:
;             pm.max_children           - the maximum number of children that
;                                         can be alive at the same time.
;             pm.process_idle_timeout   - The number of seconds after which
;                                         an idle process will be killed.
; Note: This value is mandatory.
pm = ondemand

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don't
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
pm.max_children = 5

By setting the process manager to ondemand and the maximum number of children to 5 (lines #25 and #36), we limit the amount RAM memory consumed. Bear in mind that suitable values for these parameters will not only depend on the amount of RAM memory, but also the expected server load.

Do not forget restating PHP-FPM and Nginx.

$ sudo /etc/init.d/php5-fpm restart
$ sudo /etc/init.d/nginx restart

4. Protect your WordPress blog from XML-RPC attacks.

XML-RPC attacks against WordPress websites are common. Some of them are potentially dangerous or can easily knock out your server.

One of these threats is brute force amplification attacks [6]. A quick fix against them is banning access to xmlrpc.php. This can be done by adding the following lines to the server section of the file /etc/nginx/sites-available/my-wp.

server {
...
   location = /xmlrpc.php {
      deny all;
      access_log off;
      log_not_found off;
   }
}

Unfortunately this fix is not for free: some WordPress functions will not be available [7]. Fail2ban might provide – I have not tested it – a more fine grained solution [8].


references

[1] Netcraft April 2016 Web Server Survey (http://goo.gl/Hj0402)

[2] An interesting comparison between Apache and Nginx is “Apache vs Nginx: Practical Considerations.” (https://goo.gl/3xddkw)

[3] http://php-fpm.org/

[4] “Performance of unix sockets vs TCP ports” (http://goo.gl/znDIf2)

[5] “A better way to run PHP-FPM” (https://goo.gl/jPGCtn)

[6] “Brute Force Amplification Attacks Against WordPress XMLRPC” (https://goo.gl/E7ug3d)

[7] “Should You Disable XML-RPC on WordPress?” (https://goo.gl/BnXkum)

[8] “Keep WordPress Safe From Brute Force Attacks with Fail2ban” (https://goo.gl/9QvNSf)

0 Comments on WordPress: a recipe for switching from Apache to Nginx.

Respond

Respond

Comments

Comments