Features PHP-FPM Lead image: © conceptw, 123RF.com
© conceptw, 123RF.com
 

The PHP FastCGI process manager, PHP-FPM

Substitute

Apache web servers generally use the mod_php module to run PHP programs, but the official PHP package also contains a little-acclaimed alternative by the name of PHP-FPM, which uses the FastCGI interface and has some very interesting properties. By Tim Schürmann

If you open the PHP documentation at the Installation chapter, you are likely to come across the mod_php Apache module first. It integrates the PHP interpreter with the Apache web server, thus allowing the server to execute PHP applications. The Lighttpd and IIS documentation describes a PHP interpreter that uses the FastCGI interface (see the "CGI Accelerator" box). Further into the docs, until you will find the FastCGI Process Manager, which is an alternative PHP interpreter that eliminates a few serious disadvantages of mod_php and of the traditional FastCGI interpreter.

Short Process

The mod_php module is quickly set up and enabled, but this means running the interpreter with the web server's account. Changing the configuration also necessitates a restart of the entire web server, thus causing downtime for the website. Mod_php also scales pretty badly, which you will notice if the number of hits slowly increases in the course of time. All these disadvantages were a pain for Andrei Nigmatulin, so he developed a patch for the FastCGI interpreter that added a few missing security features at the same time. He dubbed the enhanced interpreter FastCGI Process Manager, or PHP-FPM [2].

PHP-FPM starts several PHP interpreter processes that run permanently in the background, waiting for work, picking up incoming requests from the web server, and distributing them to the PHP interpreters (Figure 2).

The web server uses either a TCP port or a Unix socket to pass the request to PHP-FPM, which in turn hands it over to a PHP interpreter process.
Figure 2: The web server uses either a TCP port or a Unix socket to pass the request to PHP-FPM, which in turn hands it over to a PHP interpreter process.

To conserve server resources, PHP-FPM terminates any interpreter processes that are not needed and kills any PHP interpreter processes that take too long to complete. Additionally, you can change the configuration on the fly without the need to restart an interpreter process or even the whole web server. PHP-FPM also offers advanced logging functions; for example, it can log any scripts that exceed a certain run time, along with details of the underlying PHP function, to help in troubleshooting during development and to reveal attacks.

The PHP interpreter processes can be grouped in pools, and each pool can be a separate PHP configuration. You can even lock its processes in a chroot jail. Additionally, the administrator can specify an account under which the pool processes run. Incoming requests can then be passed to one of the pools in a targeted way. For example, you could tailor a pool for the blog, another for the forum, and a third for the official website. Because each pool responds to requests for its own TCP address, you can outsource the associated pool to a more powerful computer as the load on the forum increases – this process only involves a few simple changes in the configuration files.

PHP-FPM can even cope with add-on modules, such as eAccelerator or APC, that buffer the compiled source code (opcode). This support for add-on modules saves recompiling for the next request, which in turn accelerates the process of building the page. However, an interpreter process can (accidentally) overwrite or destroy the opcode cache. PHP-FPM therefore monitors the cache and restarts the process automatically if something seems to be going haywire.

Launch Ramp

PHP-FPM has been part of the official PHP package since PHP 5.4.0 and is therefore also included by most current Linux distributions and available through their package managers. The package usually goes by the name of php5-fpm.

One of the few exceptions is Debian 6 (squeeze); however, you can add PHP-FPM via the Dotdeb repository by adding the following line to your /etc/apt/sources.list:

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

Now you can install the required package:

apt-get update
apt-get install php5-fpm

Because you can freely assign the TCP ports used by the pools, you can run PHP-FPM parallel to other FastCGI interpreters.

PHP-FPM is tailored for Linux, which explains why it is missing from the Windows PHP package. (The php-cgi.exe in the package is simply the legacy FastCGI interpreter.) Windows administrators can either build and operate PHP-FPM manually in a Cygwin environment, which is fairly convoluted, or run PHP-FPM separately from the web server on its own Linux machine.

Running PHP-FPM on its own Linux machine is far easier, especially because you can run the Linux system with PHP-FPM on a virtual machine. This article focuses on commissioning PHP-FPM on Linux.

All of the configuration files for PHP-FPM and its PHP interpreter processes reside in the /etc/php5/fpm directory (unless your own distribution stipulates some other directory). PHP-FPM is configured in the php-fpm.conf configuration file. In the simplest case, a single line,

include=/etc/php5/fpm/pool.d/*.conf

points to all other configuration files. Incidentally, comments always begin with a semicolon and continue to the end of the line, so instead of php-fpm.conf, you will often find a sample file. On openSUSE, its name is php-fpm.conf.default. All you need to do is rename this appropriately; changes are not normally necessary.

Building a Pool

PHP-FPM groups the PHP interpreter processes into pools; at least one pool must exist. Each pool has exactly one configuration file with a .conf extension in the /etc/php5/pool.d subdirectory. Because PHP-FPM simply parses all the .conf files in this directory one after another, you can choose the filenames freely. In other words, to temporarily disable a pool, you just need to rename the extension of the associated configuration file.

If you use your distribution's package manager to install PHP-FPM, you will typically discover that a file for a simple pool already exists in the configuration directory; on Debian, for example, this is www.conf. You should not delete this file, but back it up: The detailed comments it contains explain all the possible settings, so it acts as a reference. In contrast, the information on the PHP-FPM website [2] and in the PHP documentation [3] is outdated or incomplete. If you cannot find an example file, download the official PHP source code package and take a look at the php-fpm.conf.in file in the sapi/fpm directory.

Listing 1 shows an example of a minimal configuration file: Initially, the pool named in the square brackets is apool. The interpreter processes groups then processes all requests passed into PHP-FPM on TCP port 9000 by the web server. The next line, listen.allowed_clients, ensures that only programs running on the same computer as PHP-FPM can talk to port 9000 (localhost, IP address 127.0.0.1) to keep external attackers from flooding PHP-FPM with requests or abusing the PHP interpreter. If you want to allow requests from other IP addresses, simply provide a comma-separated list on one line.

Listing 1: A Simple Pool

<§§nonumber>
[apool]
; Pool accessible on TCP port:
listen = 127.0.0.1:9000
; Access only allowed from:
listen.allowed_clients = 127.0.0.1
; Processes run under user and group:
user = www-data
group = www-data
; Create processes as needed:
pm = dynamic
; Maximum number of processes:
pm.max_children = 96
; Start with this number of processes:
pm.start_servers = 16
; Stock of interpreters without a task:
pm.min_spare_servers = 10
pm.max_spare_servers = 70

All PHP interpreter processes from apool run with the rights of the www-data user from the www-data group, as defined in Listing 1 – I am using the web server account for simplicity's sake. In a production environment, you would create a new user for each pool just to be safe.

To avoid wasting resources, you want PHP-FPM to start and stop interpreter processes as needed (dynamic). As defined by pm.max_children, this pool is not allowed to run more than 96 PHP interpreter processes at the same time. Because each interpreter process handles one request, the pool in Listing 1 can consequently handle 96 requests at the same time. pm.start_servers defines the number of child processes PHP-FPM enables directly when launched – Listing 1 sets this to 16. Note that this is the number of child threads; along with the initial process run by the administrator, this takes the total to 17. A maximum of 70 idle PHP interpreter processes is allowed (pm.max_spare_servers). To be prepared for mass requests, Listing 1 always leaves 10 processes on call (pm.min_spare_servers).

One for All

The interpreter processes launched by PHP-FPM take their settings from the /etc/php5/fpm/php.ini file. Their content reflects the well-known php.ini file; however, it now applies for all processes in all pools. For example, if you increase the memory_limit to 64M, each PHP interpreter process can then allocate 64MB to its PHP script. If only the pool from Listing 1 exists, a maximum of 96 processes can be running at the same time; in the worst case, this would mean having 96x64MB = 6GB of RAM.

If you want the processes in a given pool to use different settings, you store them in the pool configuration file (like that in Listing 1); however, the syntax is a little unusual. The name of the PHP setting is set in the square brackets of php_admin_value[]:

php_admin_value[memory_limit] = 32M

This line sets memory_limit to 32MB. Boolean values (such as settings that switch something on or off) can be overwritten in a similar way using php_admin_flag[]; an example for log_errors is:

php_admin_flag[log_errors] = on

After completing the configuration, the following call starts PHP-FPM:

/etc/init.d/php5-fpm start

The interpreter processes now run in the background, listening for requests. You can check this with:

netstat -tapn

The output should show PHP-FPM, as in Figure 3. ps -A should also reveal the specified number of PHP interpreter processes by the name of php-fpm (Figure 4). The next step is to introduce the web server to PHP-FPM.

PHP-FPM listening on TCP port 9000.
Figure 3: PHP-FPM listening on TCP port 9000.
As specified in Listing 1, 17 PHP interpreter processes are running.
Figure 4: As specified in Listing 1, 17 PHP interpreter processes are running.

Apache has to pass on the incoming requests to PHP-FPM. The Apache mod_fastcgi module handles the task of passing on incoming requests. Windows administrators can retrieve a prebuilt DLL file from the FastCGI site [4]. On Linux, check out your package manager: in Debian 6, the module is found in the libapache2-mod-fastcgi package (from the non-free repository). Be careful not to download the almost identically named mod_fcgid module. This adds FastCGI to Apache 2, but with its own process manager; it cannot pass incoming requests on to an existing interpreter such as PHP-FPM, which makes it useless in this case.

If you use Debian 6 and have not yet installed Apache 2, you can type the following command to install the necessary packages:

apt-get install apache2-mpm-worker \
   libapache2-mod-fastcgi

Next, the a2enmod fastcgi command enables the module. The mod_php5 module can be disabled – if it is installed at all – with a2dismod php5.

Now you only have to add the rows from Listing 2 in the httpd.conf Apache configuration file. On Debian 6, it is advisable to save these instructions in a separate file (e.g., named php5-fcgi.conf) in the /etc/apache2/conf.d directory. The FastCgiExternalServer line is crucial; it tells Apache to pass on requests for TCP port 9000. If mod_php was previously enabled, you need to delete its configuration or comment it out. Now, type /etc/init.d/apache2 to restart the web server. For test purposes, drop the simple PHP file info.php with

<?php phpinfo(); ?>

into the document root; on Debian 6, this is the /var/www directory. Now, when you access this file in your browser, it should return PHP-FPM as the interpreter, as shown in Figure 5.

Listing 2: Apache 2 PHP-FPM Configuration

<§§nonumber>
<IfModule mod_fastcgi.c>
  AddHandler php5-fcgi .php
  Action php5-fcgi /php5-fcgi
  Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
  FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization
</IfModule>
phpinfo() reveals that PHP-FPM responded to the request.
Figure 5: phpinfo() reveals that PHP-FPM responded to the request.

Sockets

In Listing 1, the web server uses TCP port 9000 to pass incoming requests to PHP-FPM. If the web server and PHP-FPM are running on the same machine, it makes sense to use Unix sockets as an alternative. Unix sockets reduce the TCP overhead. To allow PHP-FPM to listen on the Unix /tmp/php5-fpm.socket, you need to replace the listen line in the pool configuration file from Listing 1 with

listen = /tmp/php5-fpm.socket

Next, replace lists.allowed_clients with the following three lines:

listen.owner = www-data
listen.group = www-data
listen.mode = 0660

These lines define the owner (lists.owner), the group (lists.group), and the permissions (lists.mode) for the socket. To restart PHP-FPM, issue the command:

/etc/init.d/php5-fpm restart

You should now have a /tmp/php5-fpm.sock file that belongs to the www-data user with permissions of 0660.

In the Apache 2 configuration, replace the line that starts FastCgiExternalServer with:

FastCgiExternalServer \
   /usr/lib/cgi-bin/php5-fcgi \
   -socket /tmp/php5-fpm.sock \
   -pass-header Authorization

and restart the Apache server.

Elegant Restart

With mod_php, to apply changes to the PHP configuration, you must restart the Apache server. Any active users experience a disconnect and see an error message. PHP-FPM does not require a restart: You can modify the configuration on the fly. This is especially useful if you want to increase the number of interpreters in the pool or add more pools. For new incoming requests, PHP-FPM then starts new processes with the updated configuration. The processes for existing requests complete their work before PHP-FPM terminates them. For a graceful restart of this kind, enter:

/etc/init.d/php5-fpm reload

As mentioned,

/etc/init.d/php5-fpm restart

triggers a conventional restart of PHP-FPM.

Minute Taker

PHP-FPM can record the requests processed by a pool for diagnostic purposes. To make it do this, add the following the line to the appropriate pool configuration file (e.g., the simple pool configuration file in Listing 1):

access.log = /var/log/$pool.access.log

The logfile name follows the equals sign. PHP-FPM replaces the $pool placeholder with the name of the pool. By default, the protocol stores the IP address, username, (server) time, request method, requested address (URI), and the returned status code. However, PHP-FPM can also log the much more interesting execution time, CPU load, and query string.

Thus, you will want to collect all of this interesting information with an additional line,

access.format = \
   "%R - %u %t \"%m %r%Q%q\" \
   %s %f %{mili}d %{kilo}M %C%%"

As its cryptic structure suggests, you can define the protocol structure as in Apache; the letters are placeholders for the corresponding information. The comments in the example file that comes with the distribution reveal which placeholder stores what information.

Whereas the default access log records all the requests (Figure 6); the slow log records requests for which the PHP interpreter took too long. PHP-FPM keeps a slow log for a pool if you add the following line to the associated configuration file,

This access log shows standard information at the top and details of the requests below.
Figure 6: This access log shows standard information at the top and details of the requests below.
slowlog = /var/log/$pool.log.slow

where $pool stands for the pool name. The line

request_slowlog_timeout = 30s

defines how long processing a request must take to be regarded as too slow. In this case, PHP-FPM will add the request to the slow log if the PHP interpreter has not returned a response after more than half a minute (Figure 7). As an alternative to s for seconds, you can also state the time in minutes (abbreviation m ), hours (h), and days (d).

As this slow log reveals, a sleep() statement in info.php is preventing expeditious processing of the request.
Figure 7: As this slow log reveals, a sleep() statement in info.php is preventing expeditious processing of the request.

PHP-FPM records internal problems and errors in /var/log/php5-fpm.log by default. The php5-fpm.log file is also where you will find information on faulty configuration files.

Jail

If you are interested in web server security, you can banish all interpreter processes in a pool to a chroot jail; just put the line

chroot = /my/jail

in the pool configuration file. The equals sign is followed by the absolute path to be used as the new root directory for the processes – in this example /my/jail.

To query the current pool health via a special URL, add the following line to the pool configuration (from Listing 1):

pm.status_path = /status

Among other things, http://www.example.com/status tells you whether the processes in the pools are currently running and the number of requests to which these processes have responded. By default, PHP-FPM returns this information as plain text. However, you can wrap the information in HTML or XML by simply adding ?html or ?xml to the URL. With the following command:

ping.path = /ping

you can check whether the pool is running in http://www.example.com/ping. In this case, PHP-FPM responds with a simple pong. A word of caution: This is precisely the kind of information leak that attackers look for; thus, you will want to disable this function in production use, or at least restrict access by the web server.

Emergency Stop

If an interpreter process takes too long to complete a PHP script, PHP-FPM automatically terminates it. You can define the number of seconds for this in the /etc/php5/fpm/php-fpm.conf configuration file by adding the line:

process_control_timeout = 10s

In this case, if a process does not respond for 10 seconds, PHP-FPM terminates it automatically. Again, you can use the abbreviations m for minutes, h for hours, and d for days.

Parallel Processing

PHP-FPM provides a new PHP function called fastcgi_finish_request(). As its name implies, fastcgi_finish_request() exits the request, but the script continues to run in the background. An example of a practical application for this technique is given in Listing 3; however, fastcgi_finish_request() only exists in PHP-FPM; other PHP interpreters will not run the script.

Listing 3: fastcgi_finish_request()

01 <?PHP
02 //...
03
04 if(!is_file('preview.png'))
05 {
06   echo "Creating a preview, please re-load after 10 seconds";
07   // Terminate request...
08   fastcgi_finish_request();
09   // ... but still continue processing this:
10   create_preview();
11 }
12 else show('preview.png');
13
14 //...
15
16 ?>

Conclusions

PHP-FPM enhances security and scales well, although its approach does not automatically accelerate delivery times, compared with the conventional FastCGI interpreter.

The impressive figures published on the Internet usually pertain to comparisons of Apache 2 with mod_php to the combination of a small and lean web server such as Nginx with PHP-FPM.

If you want to accelerate the response speed for your PHP-FPM configuration, it is important to carry out additional optimizations.