Apache mod_proxy_fcgi and php-fpm

To anyone who’s been following developments in the web hosting world, the existence of FastCGI shouldn’t come as a surprise. Neither should the fact that the PHP folks provide a FastCGI process manager called php-fpm. Apache 2.4 comes with mod_proxy_fcgi which makes connecting all of that to Apache relatively easy (for sufficiently recent versions of Apache 2.4). What isn’t easy to find is a nice, simple, recipe for plumbing it all together without undesirable side effects.You’ll likely find a number of methods of configuring Apache to talk to php-fpm using mod_proxy_fcgi. These pretty much all depend on a recent version of Apache 2.4 which has a lot of the warts removed from the proxying system. If you just want the working recipe, jump down to the bottom of the page. I’m going to go through several methods that seem to work on the surface and then explain why they just plain suck.

What I am not going to discuss is exactly how to specify the proxy configuration string. This is one aspect the documentation is clear on and pretty much everyone agrees on that part of it. I’ll use simply “proxy:fcgi://locahost” as the base proxy string.

First, there is the ProxyPass method. There are two variants of this as shown below (PATH represents the actual path where the php files will be found).

ProxyPass "/app/" "proxy:fcgi://localhost/PATH"
ProxyPassMatch "\.php(/\.*)?$" "proxy:fcgi://localhost/PATH"

The first variation routes an entire path to php-fpm. It then leaves what to do with any item under there up to php-fpm, including any files that don’t exist. It also means you can’t have Apache handle non-php files under “/app/”. However, if your application is prepared for this, this method is reasonably good. It is not a good general purpose solution, though.

The second variation is a bit more flexible. It will pass any request string with “.php” somewhere in it to php-fpm but it will allow Apache to sort out anything else. This method seems to be the most popular one you find when you search for things. It’s also the second example in the mod_proxy_fcgi documentation from Apache as of this writing which makes it more likely to be selected since people will use it, see their PHP scripts work, pat themselves on the back, and go about their day.

There is, however, a major problem or two with this. First, it will pass all requests for something that looks like a PHP file to php-fpm, even if there is no such file. It will also try to execute image files and the like as PHP with carefully crafted URLs. That can allow arbitrary files uploaded by users to be executed even if it looks like they shouldn’t be. In other words, this is horribly insecure. Don’t do it. Period.

Another method of passing PHP files to php-fpm involves using mod_rewrite to do the same. There are a number of ways of doing that which I am not going to detail here. These usually involve the “P” flag, or with a sufficiently recent Apache version, the “H” flag. This method is quite powerful and flexible. It can be done perfectly securely when configured for specific web sites. However, it breaks down badly when you need to allow general hosting where people want their rewrite rules for WordPress, etc., to actually do something useful and not have surprising interactions. These methods are also complicated and require understanding the somewhat arcane concepts behind mod_rewrite. Note that this is not knocking mod_rewrite which is an excellent tool.

With more modern Apache versions, there are two new ways you can pass PHP files off to php-fpm:

AddHandler "proxy:fcgi://localhost" php
<FilesMatch "\.php$">    SetHandler "proxy:fcgi://localhost"</FilesMatch>

Okay. The first one tells Apache to pass anything that has a “php” extension on it to php-fpm. The second does the same but gives more flexibility in selecting the files that match.

Both of these are substantially better than the ProxyPassMatch option above for one reason: they’re simpler since you don’t need to mess about with file paths for your proxy string. You also don’t need to take into account the possible existence of a PATH_INFO string.

Both of these will, however, pass nonexisting things that look like PHP files to php-fpm. That is, of course, not useful because it means Apache doesn’t get the opportunity to serve up your custom crafted ErrorDocument page. Instead, you’ll get a boring “file does not exist” type message from php-fpm which will likely clash with your web site design.

So how do you get Apache to check if the file exists before it packages up the request for php-fpm? This is the part that took me hours of googling around until I stumbled on the right incantation to offer in the Alphabet temple. There are two ways to do this. First is the way that looks like it works but doesn’t do what you think it does. Second is the way that actually works.

The first thing you might be temped to try is “ProxyErrorOverride On”. Don’t do that unless you really do know what you’re doing. If you have PHP scripts that generate custom not found pages and set the response code to 404, those will be overridden by this directive. Also, it still passes those non-existing requests to php-fpm in the first place. In other words, <hand gesture> this isn’t the setting you are looking for.

Eventually, I stumbled on an example that actually does work:

<FilesMatch "\.php$">    <If "-f %{SCRIPT_FILENAME}">        SetHandler "proxy:fcgi://localhost"    </If></FilesMatch>

Okay, that looks a bit bizarre, doesn’t it? However, it’s actually quite straight forward and it has the real advantage that it actually works. (Presumably, a Files block would also work.) What it does is uses the magic <If> container provided by Apache to only set the handler for your fancy PHP scripts if the script file actually exists. Otherwise, processing continues as usual for a file that doesn’t exist. Now, with this recipe, everything behaves as you expect it to. PHP scripts are passed off to php-fpm while anything else is handled by Apache as usual. There are also no shenanigans about trying to execute a file that doesn’t exist so it doesn’t mess up your carefully crafted ErrorDocument handler.

So there you have it. With a modern Apache 2.4 web server, you can actually configure it to talk to php-fpm in a manner that doesn’t have a stack of dragons lurking in every corner.

Leave a Reply

Your email address will not be published. Required fields are marked *