What’s php-fpm? It’s a simple and performant way of running PHP as a certain user in a chroot’ed environment. It’s part of PHP and comes packetized with Debian Wheezy. However, /usr/share/doc/php-fpm/* isn’t too elaborate on setting it up, so here goes…
If you’re reading this, your motivation will almost certainly be to put PHP-applications (Wordpress in my case) in a more restricted environment. Chroot’ing is an “inexpensive” way of doing that.
chroot != security
However, I want to point out that chroot isn’t a particularly secure concept. Why? Because it wasn’t meant to be one in the first place, as discussed (kindergarten-style) in this thread.
So, keep in mind that chroot can be circumvented. If you’re root, there’s not much to circumvent, you just step out. See this article for some background information on chroot and keep in mind that a chroot “jail” can be broken out from. If a more stringent separation is required, something like Linux Containers, OpenVZ or FreeBSD jails are the way to go.
Installing the required packages
Having said that, let’s get to the gist:
# aptitude install php5-fpm libapache2-mod-fastcgi # a2enmod fastcgi alias actions
The package libapache2-mod-fastcgi resides in Debian’s non-free repository, so it needs to be in your apt-configuration. On Ubuntu, the package can be found in Multiverse. It seems that Apache’s own FastCGI implementation (fcgi) doesn’t support communication via Unix domain sockets, that’s why an alternative needs to be used. Apache 2.3 has mod_proxy_fcgi, but Wheezy uses 2.2.
The modules are probably all enabled already, but we’re making sure they are.
First we’ll configure the handler, put it into a new file named e.g. “/etc/apache/conf.d/php-fpm”:
Action fcgi-php-fpm /fcgi-php-fpm virtual Alias /fcgi-php-fpm /fcgi-php-fpm FastCgiExternalServer /fcgi-php-fpm -socket /var/run/php5-fpm.sock -pass-header Authorization -idle-timeout 600
- This defines a system-wide Handler called “fcgi-php-fpm”. Handlers are always system-wide, so you’ll have to make up different names instead of “fcgi-php-fpm” and a different socket-name for each pool configured, see “listen” below.
- /fcgi-php-fpm is not a real file and the Alias seems to be necessary. I don’t find the configuration very intuitive, but it works. See FastCGI’s FAQ and this for details.
- The configuration could be put in an <IfModule mod_fastcgi.c>-block, but I prefer Apache bailing out over serving potentially unsafe PHP with mod_php.
- Authorization-Headers (if sent by the client) should be passed to the PHP-scripts.
Then, for each VirtualHost, Location or Directory where PHP-files should go through php-fpm, set the handler with:
<FilesMatch ".+\.ph(p?|t|tml)$"> SetHandler fcgi-php-fpm </FilesMatch> AllowOverride Limit Indexes AuthConfig
The files the handler is set on must reside somewhere within the chroot we’ll configure below. For this example, the chroot will be at “/srv/www-php-fpm”. I have placed this Files-block in a VirtualHost with a DocumentRoot of “/srv/www-php-fpm/www”.
Make sure that AllowOverride hasn’t set “FileInfo” for this Document/Location/Directory! “FileInfo” will enable a potential attacker to disable php5-fpm altogether by using “SetHandler”!
Unfortunately, “FileInfo” is required for setting up mod_rewrite. The only solution I can think of is moving those settings out from .htaccess to the configuration in /etc. If you can, set “AllowOverride” to “None”.
SetHandler is used instead of AddHandler, because the latter won’t properly override the settings from mods-available/php5.conf, which we may want to use in parallel.
Make sure that the FilesMatch-Directive matches the FilesMatch-Block in /etc/apache2/mods-enabled/php5.conf! Otherwise, an attacker may use an extension that goes through the normal handler!
If you want to be on the safe side, you can
- restrict the FilesMatch-block in php5.conf to *.php only (<Files *.php>…</Files>) or
- disable the whole FilesMatch-block there and enable the normal handler only where needed or
- disabe the php5-module altogether, which is probably the best idea if feasible in your setup.
Php-fpm has its configuration files in /etc/php5/fpm. Except php-fpm.conf and php.ini, multiple “pools” running under different users and chroots can be configured in “pool.d/”. Here, I’ll only use the default pool in “www.conf”. This pool will run as the normal “www-data”-user.
- user/group: User and group this pool should run as, both “www-data” in our example.
- listen: The socket this pool will listen at, leave at the default for one pool. This must obviously be the same socket as configured above.
- pm.*: The configured max_children of 5 seems really low, as the default for Apache’s prefork MPM is MaxClients=150. No matter which MPM you use, one php5-fpm child will be processing one request each, so let’s increase it. My site isn’t very busy, so I’ve set it to max=16/min_spare=4/max_spare=8. Start_servers can be commented out, it will be calculated.
- Uncommenting “max_requests” seems like a good idea. Even if no memory leaks are anywhere (yeah right), recycling a child after a couple of hundred or thousand requests probably won’t hurt performance at all.
- chroot: Set this to the path you want the chroot to be at. Here, it’s “/srv/www-php-fpm”.
Symlink-workaround and testing
Restart php5-fpm (“service php5-fpm restart”) and put an index.php into the top-level-directory with
Point your browser to the site and you’ll probably get a “File not found”-message. This is due to a bug in php-fpm, which doesn’t rewrite SCRIPT_FILENAME and other env-vars, which it probably should. If this bug is ever fixed and the fix makes it into Debian, this workaround is not needed.
What happens is that php-fpm doesn’t care it’s in a chroot and tries opening “/srv/www-php-fpm/www/index.php”, as told by Apache over FastCGI. Of course, if the chroot is at “/srv/www-php-fpm”, this path doesn’t exist.
We’ll use a simple workaround: Making a relative symlink from “/srv/www-php-fpm/srv/www-php-fpm” to “/srv/www-php-fpm” like this:
$ cd /srv/www-php-fpm $ mkdir srv $ ln -s .. srv/www-php-fpm $ ls -l srv/ lrwxrwxrwx 1 root root 2 May 5 00:26 www-php-fpm -> .. $ ls /srv/www-php-fpm/srv/www-php-fpm/www index.php
Then, “/srv/www-php-fpm/www/index.php” is also a valid path within the chroot and index.php will happily tell you that the “Server API” is now “FPM/FastCGI”.
If any of your scripts need to run longer than 30 seconds, timeouts need to be adjusted:
FastCGI: comm with server "/fcgi-php-fpm" aborted: idle timeout (30 sec), referer: ... FastCGI: incomplete headers (0 bytes) received from server "/fcgi-php-fpm",
This is the FastCGI side terminating the connection. I think it’s better if the php-fpm side is in charge of timeouts.
I’ve found that max_execution_time in fpm’s php.ini doesn’t work, but request_terminate_timeout in the pool-conf does.
Set it to whatever suits you and set the idle-timeout parameter in the FastCgiExternalServer above statement to something longer.
Note that the idle-timeout parameter wasn’t there before this section has been added.
Further work needed
Don’t fret, there’s more work to do. Within this chroot, PHP won’t work too well yet. See the next post about further setting up the chroot.
- 2013-05-13: Updated the SetHandler-block in Configuring Apache to reset the handler on all possible PHP-extensions
- 2013-09-28: Tested instructions with Ubuntu 12.04 LTS
- 2015-10-07: Added the “Timeouts” section