Adam Innes · Blog

PHP-FPM Just Shipped in PHP 5.3.3

· 3 min · php, php-fpm, nginx, apache, fastcgi

If you run PHP behind nginx, you know the dance. Nginx doesn’t embed PHP the way Apache does with mod_php, so you start a pile of php-cgi processes listening on a port, set PHP_FCGI_CHILDREN so it forks enough workers, set PHP_FCGI_MAX_REQUESTS so they recycle before leaking too much memory, and then write an init script so it all comes back after a reboot. When a worker wedges on a slow request, you find out from your users.

PHP 5.3.3 came out on July 22, and one line in the release announcement is easy to skim past: “Added FastCGI Process Manager (FPM) SAPI.” FPM has been around for a while as a separate patch (the credits list Andrei Nigmatulin, dreamcat4, Antony Dovgal and Jerome Loyet), and now it lives in the PHP source tree.

Read the fine print first

It isn’t built by default. You have to pass --enable-fpm to configure, and the help text for that flag in the 5.3.3 config.m4 literally says “EXPERIMENTAL”. The build also needs libevent 1.4.11 or newer, and configure stops with an error if it can’t find it (--with-libevent-dir points it at a custom prefix). So this is not “flip it on in production tonight” territory. It is “build it on a staging box this week” territory.

What FPM does that php-cgi doesn’t

FPM runs a master process that owns a set of worker pools. Each pool gets its own listen address (a TCP port or a Unix socket), its own user and group, and its own process manager settings. That alone is a nice security win: two sites on one box can run as two different users without two separate hand-built spawning setups.

The part I like most is the operational stuff in the sample php-fpm.conf. Set request_slowlog_timeout and FPM dumps a PHP backtrace to the slowlog file for any request that runs longer than that, which is exactly the thing you wish you had when a page randomly takes 30 seconds. request_terminate_timeout kills a worker that blows past a hard limit even when max_execution_time doesn’t stop it. emergency_restart_threshold and emergency_restart_interval restart FPM if too many children die with SIGSEGV or SIGBUS in a short window. There is also an optional status page (pm.status_path) and a ping URL for health checks, and the bundled init script’s reload sends the master a USR2 so it reloads without you killing everything by hand.

The pm settings

This is the section people will copy and paste without reading, so here is what they mean. pm is either static or dynamic. With static, FPM keeps exactly pm.max_children workers alive. With dynamic, pm.max_children becomes the ceiling, pm.start_servers is how many it starts with, and FPM tries to keep the number of idle workers between pm.min_spare_servers and pm.max_spare_servers, spawning when idle workers drop below the minimum and killing some when they rise above the maximum.

[www]
listen = 127.0.0.1:9000
user = www-data
group = www-data
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm.www.slow.log

The sample config calls pm.max_children the equivalent of Apache’s MaxClients with prefork and of PHP_FCGI_CHILDREN, and that is how you should size it: it caps how many PHP requests run at once. My advice is to look at how much memory one of your PHP workers really uses under load, divide the RAM you can spare by that number, and go a bit lower. A max_children value that looks generous on paper turns into swapping the moment traffic spikes. pm.max_requests is the old PHP_FCGI_MAX_REQUESTS, and it’s still worth setting if some extension you rely on leaks.

Wiring up the web server

On nginx it looks like any other FastCGI backend:

location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
}

The try_files line makes nginx return a 404 for PHP files that don’t exist instead of handing PHP a path and letting it guess. Read the cgi.fix_pathinfo comment in your php.ini before you decide you don’t need it.

On Apache you can use the third party mod_fastcgi module, whose FastCgiExternalServer directive exists for exactly this case: it points a filename at an external FastCGI application that Apache doesn’t start or manage, which is FPM’s job now. Pair that with an Action and AddHandler for .php and Apache hands PHP requests to the pool. That gets you a threaded or worker MPM on the Apache side without dragging mod_php into every Apache process.

Should you switch?

I’d start testing now and wait a release or two before betting production on it, mostly because PHP’s own configure flag still says experimental. But the direction is clearly right. Process management for PHP is finally part of PHP instead of something every team reinvents in a shell script, and the slowlog alone makes it worth a staging build.

← all posts