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.

3 thoughts on “Apache mod_proxy_fcgi and php-fpm”

  1. Hey. Enjoyed your effective sum up. Pretty much everything I just got done coming round to, only moments before finding your article.

    However, after I’d implemented the method… and believing the issue solved, I discovered I was still experiencing problems.

    It did indeed send non-existent .php filenames to the proper 404 error page, but I found an issue when I noticed search bots doing odd things like hitting domain.com/existing_filename.php/existing_or_not_filename.php.

    Bizarre. Those seem to PASS the test (because of the first legit filename) and end up sending it all to php-fpm, which then shows “No Input file specified” for bad filenames ending in .php.. or “Access Denied” for other bad file/folder names…. AND if the 2nd filename actually exists in the root folder (e.g. existing_filename1.php/existing_filename2.php) it will somehow *mostly* render the 2nd filename as if the 1st filename in the path were ignored… with the hiccup of being unable to show certain images and whatnot because it looks for them in domain.com/existing_filename1.php/images/image.jpg. Weird, eh?

    Have I got something completely botched in my vhost .conf file? Or is this just a happy, but little noticed, consequence of using the method? (NOTE: I actually originally had REQUESTED_FILENAME, but changed it to SCRIPT_ as a test, just in case yours worked better. Same result.) Need to check PATH too?

    Cheers!
    p.s. you’re not related to Steve A. are you?

    1. I would guess you have an interaction with some other setting in your Apache or PHP configuration or you have bad links in your html output. It might be in a .htaccess file. A good candidate might be bizarre mod_rewrite rules, mod_speling, or the MultiViews option.

      With an existing PHP file as the first part of the path, the result *should* be passed to php-fpm with the existing PHP file being the script to execute and the rest after that showing up in PATH_INFO. That’s the point of PATH_INFO. A non-existing file shouldn’t match at all and, thus, shouldn’t trigger the proxy.

      I don’t have the behaviour you describe with version 2.4.20 of Apache. Granted, I should probably upgrade, but were at least a couple of issues with the public release after 2.4.20 such that the changelog for 2.4.27 says there was a “regression” releated to php-fpm. That makes me a bit gun-shy. However, if you’re on a version between 2.4.20 and 2.4.27, upgrading might be your answer.

Leave a Reply

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