Mar 082012
 

This article of mine has been original published on Wazi

WordPress, the popular content managing system (CMS), is easy to set up and use, and well supported by both its community and professional consultants. WordPress depends upon a complete stack that comprises an operating system, database, web server, and PHP. If you can optimize this stack, you can enhance the performance of your site. Here are some tricks and best practices for a setup that will improve your throughput without forcing you to upgrade your hardware.

In the Part 1 of this guide we have seen what to check and change in the Operating system and on the database server (mysql), today we’ll see how to setup the http server (Nginx to be exact) and PHP.



My HTTP server of choice is nginx; in a recent article I talked about some advantages of using this HTTP server with PHP5 in FPM mode versus Apache and mod_php module. If you’ve already added the EPEL repository, runyum install nginx to install nginx 0.8.54-1.el6. While the latest stable release of nginx is currently above the 1.0.x, installing a package from an official repository gives you a more stable but older version.

Now you should look at a few parameters in the /etc/nginx/nginx.conf configuration file.

worker_processes controls – surprise – the number of worker processes to spawn. A worker is similar to a child process in Apache. Nginx has the ability to put worker processes to use on SMP multiprocessor machines to decrease latency when workers are blocked by disk I/O, or to limit the number of connections per process when select() or poll() is used. The general rule of the thumb is to set the number of nginx workers to two, or the number of CPUs your server has. If you are going to serve sites with a lot of static content, add more workers – up to one per disk.

If your disk subsystem is poor or the load is too high, nginx worker processes may become locked on I/O operations and unable to serve other requests. Run ps ax and examine its output; workers that are in “D” state are locked. Increase the number of worker processes until ps ax returns a number of worker processes not blocked equal to the number of CPUs on your system. You can also add some memory for disk cache to solve this problem.

Along with worker_proceses, worker_connections allows you to calculate a max_clients value:
max_clients = worker_processes * worker_connections 
I suggest not making this value too high. If your ulimit -n output is something like 1024, then your worker connections would need to be set to 1024 or less (maybe even 768), and its unlikely that you’ll have “worker_processes x 1024″ simultaneous connections.

Timeouts values specify the amount of time in seconds that nginx will wait for the client to complete the specified action.

client_body_timeout N is the read timeout for the request body from client. If after this time the client sends nothing, nginx returns the error “Request time out” (408).

client_header_timeout N is the timeout reading the title of the request of the client. If after this time the client send nothing, nginx returns the error “Request time out” (408).

keepalive_timeout N N – the first value is for keep-alive connections with the client. The second parameter assigns the value “Keep-Alive: timeout=time” in the header of answer.

Regarding these value I usually keep 30 seconds for the client timeouts and not more than 10 seconds for the keepalive. You can lower the keepalive, to 3 perhaps, if you have a busy site and you notice that you have a lot of connection open. Others suggest setting it to 0, thus making the client reopen a connection.

sendfile on|off Use sendfile when the nginx server can actually ignore the contents of the file it is sending. If set to on, it uses the kernel sendfile() support instead of its own resources on the request.

tcp_nopush on|off enables or disables the TCP_NOPUSH (FreeBSD) or TCP_CORK (Linux) socket option. Note that this option only applies if the sendfile directive is enabled. If tcp_nopush is set to on, nginx will attempt to transmit the entire HTTP response headers in a single TCP packet. Most sources suggest setting it to off.

tcp_nodelay on|off disables the Nagle buffering algorithm. You can use it when the server doesn’t require a response from the client. General web use does need a response, so this should be turned off, unless you need to send out small bursts of information, like tracking mouse movements.

multi_accept on|off tries to accept() as many connections as possible after nginx gets notification about a new connection.

You should also enable all the compression options of nginx. Insert into your nginx.conf file these options:

   worker_processes      2;
   events {
    worker_connections   768;
    use epoll;
   }
   sendfile              on;
   tcp_nopush            off;
   client_body_timeout   30;
   client_header_timeout 30;
   keepalive_timeout     10 10;
   tcp_nodelay           off;
   multi_accept          on;
   gzip on;
   gzip_proxied any;
   gzip_comp_level 2;
   gzip_disable "MSIE [1-6].(?!.*SV1)";
   gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;

These are the general options.You can also tune the specific WordPress site options. To do this, change the specific Virtuahost file. I usually give them the name of the site I’m setting up – for example, www.example.com – and I place them in the /etc/httpd/conf.d directory. Below are some suggested options if you are using php5-FPM.

.....
location ~ .php$ {
      fastcgi_split_path_info ^(.+.php)(.*)$;
      fastcgi_pass   backend;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  /var/www/$fastcgi_script_name;
		fastcgi_connect_timeout 60;
		fastcgi_send_timeout 180;
		fastcgi_read_timeout 180;
		fastcgi_buffer_size 128k;
		fastcgi_buffers 4 256k;
		fastcgi_busy_buffers_size 256k;
		fastcgi_temp_file_write_size 256k;
		fastcgi_intercept_errors on;
.....

With the changes and additions to the configuration files above, you should have a good, basic optimization of nginx.

PHP-FPM

I use PHP-FPM as an alternative PHP FastCGI implementation because it offers some useful features for sites of any size, and especially busier sites. Some of the features I find especially useful in this implementation of PHP are:

  • Advanced process management with graceful stop and start
  • The ability to start workers with different uid, gid, chroot, or environment settings and different php.ini files (replaces safe_mode)
  • Stdout and stderr logging
  • Emergency restart in case of accidental opcode cache destruction
  • Accelerated upload support
  • Support for a “slowlog”, you can log all the slow scripts.

Unfortunately, PHP-FPM is not available in the default CentOS repository, and not even in EPEL, the Extra Packages for Enterprise Linux repository we set up earlier, so we have to add another repository, this one from the IUS Community Project. The goal of this project is to provide up-to-date and regularly maintained RPM packages for the latest upstream versions of PHP, Python, MySQL, and other common software for Red Hat Enterprise Linux (RHEL).

To add IUS as a repository on a 32-bit system, run this command as root from a terminal window:

rpm -ivh http://dl.iuscommunity.org/pub/ius/stable/Redhat/6/i386/ius-release-1.0-8.ius.el6.noarch.rpm

If you have a 64-bit system, run:

rpm -ivh http://dl.iuscommunity.org/pub/ius/stable/Redhat/6/x86_64/ius-release-1.0-8.ius.el6.noarch.rpm

After adding the repository, update the list of the packages with the command yum update.

Now you can install PHP-FPM, package php53u-fpm-5.3.8-3.ius.el6.i686 to be exact, and the corresponding PHP extension to talk with MySQL with the command:

yum install php53u-fpm php53u-mysql

The defaults in the application’s generic configuration file, /etc/php-fpm.conf, are good enough, but we’ll tweak the pool configuration file. PHP-FPM can launch multiple pools of processes listening on separate ports to meet the demands of multi-domain virtual hosting environments. The pools’ configuration files are located in /etc/php-fpm.d/poolname.conf. The default pool is named www, and that’s the only pool we’ll use. The main options are:

Pool name The default is [www].

listen specifies the address on which to accept the requests. It’s usually set to a localhost high port; 9000 is the default.

listen.allowed_clients Optionally specifies a list of IPv4 addresses of  clients that are allowed to connect. If you listen only on localhost you can safely skip this.

user and group Put your web server’s user and group here. For us, this is nginx/nginx.

pm specifies the type of process manager. The options are static or dynamic; I use dynamic so I can start and stop more processes as needed.

pm.max_children sets the limit on the number of simultaneous requests that will be served. It controls the number of child processes to be created when pm is set to “static,” and the maximum number of child processes to be created when pm is set to “dynamic.” It’s equivalent to the ApacheMaxClients directive you may be familiar with if you use mpm_prefork. The best value for this variable depends on how much RAM you have on your system and how much RAM you have set up for each PHP process. If you’ve set 32MB as the max limit of RAM for PHP, having a value of 20 here means that you could use up to 640MB of RAM for all the PHP-FPM processes.

pm.start_servers defines the number of child processes created on startup.

pm.min_spare_servers is the desired minimum number of idle server processes. You want some spare server processes ready to answer clients, but not too many.

pm.max_spare_servers is the desired maximum number of idle server processes.

pm.max_requests controls the number of requests each child process should execute before respawning. This directive can be useful to work around memory leaks in third-party libraries. By default it’s not activated.

request_terminate_timeout specifies the timeout in seconds for serving a single request after which a worker process will be killed. By default it’s not set, but I suggest putting a value here. Depending on the contents of your site it could be anything from 30 and 300; a fast WordPress site should be able to serve all request withing 30 seconds.

request_slowlog_timeout specifies a value in seconds (or minutes) that indicates a timeout for serving a single request, after which a PHP backtrace is to be dumped to the slowlog file. Set this to 1 or 2 seconds less than your request_terminate_timeout to see which PHP processes are slow and so are terminated, as long as the previous directive is activated as well.

slowlog defines the path to the log file where you can see slow requests.

By default PHP-FPM uses all the values defined in the file /etc/php.ini, but you can put in this file additional php.ini definitions that will be specific to this pool of workers. These definitions will override the values previously defined in the php.ini file. One useful setting for WordPress is php_admin_value[memory_limit], which specifies the maximum amount of memory that PHP can use in megabytes. Depending on the total RAM available on your server, this value can range from 32 to 512.

Here are a few example configuration settings that serve as good starting points for servers of different memory sizes running both MySQL and a web server on the same machine.

Server with 512MB RAM:

[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
user = nginx
group = nginx
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
request_terminate_timeout = 30
request_slowlog_timeout = 28
slowlog = /var/log/php-fpm/www-slow.log
php_admin_value[memory_limit] = 32M

Server with 1GB RAM:

[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
user = nginx
group = nginx
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500
request_terminate_timeout = 45
request_slowlog_timeout = 40
slowlog = /var/log/php-fpm/www-slow.log
php_admin_value[memory_limit] = 48M

Server with 2GB RAM:

[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
user = nginx
group = nginx
pm = dynamic
pm.max_children = 40
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500
request_terminate_timeout = 60
request_slowlog_timeout = 40
slowlog = /var/log/php-fpm/www-slow.log
php_admin_value[memory_limit] = 64M

Once your site is running you can get more information to perfect your setup. Are your scripts terminating because they reach memory_limit? Increase the value a bit. Does your system freeze because it eats up all your memory? Lower the pm.max_children parameter and/or the memory_limit. Also check the logs of nginx and php-fpm and the slowlog; they can give great information.

Popular Posts:

Flattr this!

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

*