Scatter/Gather thoughts

by Johan Petersson

Apache 2 and FastCGI

As alluded to previously, I'm looking into an Apache upgrade. The machine serving this site and several others currently runs Apache 1.3 with mod_php. I could simply upgrade to Apache 2 and keep running mod_php with the prefork MPM, but the benefit of doing so would be limited. Since an upgrade will require many changes to the system anyway, I might as well try to improve and future-proof my setup somewhat.

I'd like to run Apache 2 with the worker MPM to improve the efficiency of regular file serving. PHP support is needed and mod_php doesn't work well with multithreaded MPMs, but I choose to see this as an opportunity rather than a problem: due to security concerns I'd prefer to get rid of mod_php. It runs all PHP code in the context of the main Apache user, which is less than ideal on a server shared with other users.

One solution to the shared web server dilemma is to run PHP in CGI mode, with a setuid wrapper such as suexec. Some security-conscious web hosting companies have been known to do this, but there's a performance penalty. I'm willing to sacrifice some performance to improve the PHP security situation, but not the process-per-request overhead that running PHP in suexeced CGI mode will incur.

The other solution is FastCGI, which allows you to run PHP script engines as persistent processes alongside Apache. The FastCGI servers can be managed separately or spawned dynamically, using suexec to keep code running in different user contexts. There's obviously some overhead to this compared to having the PHP interpreter embedded in the web server. On the other hand, the resources required by the Apache processes should be greatly reduced. Or so the theory goes.

Despite the new autoconf/libtool build system in Apache 2, most third-party modules are difficult to build into the web server statically. I think this is mostly due to lack of documentation and experience with Apache 2 module development. Loading modules dynamically is great for development servers, but for production servers I prefer static modules because they make administration much easier (there are less dependencies and versioning problems to worry about).

After hacking a bit on the mod_fastcgi files I did manage to get it built into Apache, and started experimenting with getting it to run PHP as a dynamically spawned, suexeced FastCGI server:

<IfModule mod_fastcgi.c>

    FastCgiWrapper  bin/suexec
    FastCgiIpcDir   pipes
    FastCgiConfig   -singleThreshold 1 -pass-header HTTP_AUTHORIZATION

    AddHandler  fastcgi-script              .fcgi .fcg .fpl

    Action      application/x-httpd-php4    /fastcgi-bin/php4.fcgi
    Action      application/x-httpd-php5    /fastcgi-bin/php5.fcgi

    AddType     application/x-httpd-php5    .php .php5
    AddType     application/x-httpd-php4    .php4

</IfModule>

The AddType directives associate PHP script files with a MIME type, which is associated with a FastCGI application through Action directives. Whenever the fastcgi-script handler is invoked for such an application, the server will connect to an existing FastCGI server or spawn a new one using the .fcgi file. Note how easy it is to support multiple PHP versions using FastCGI. You can override MIME types in virtual hosts, e.g. while migrating to PHP 5.

The exact MIME types specified here are not important because they're only used internally (I just made some up that won't clash with anything else on my server), but they do need to look like MIME types – otherwise Apache will complain Cannot get media type in its error log. Apache doesn't know that the real MIME type will come from the FastCGI application.

FastCgiWrapper is what makes mod_fastcgi spawn applications in the context of the user/group configured for the virtual host receiving the original request. For security reasons suexec requires the program it executes to be owned by the target user/group and be located in a directory owned by the same user/group. That's obviously a problem if you want different users to share a central PHP binary, like I do.

The FastCGI FAQ suggests that you create a shell script that runs the PHP binary. While that works, needing such scripts for each virtual host user/group translates into extra support and maintenance I could live without. Besides, with this last indirection the number of layers is starting to get ridiculous.

I've opted instead to loosen the (draconian but necessary) suexec restrictions somewhat. Altering the suexec code is harshly warned against in the Apache documentation, rightly so because setuid programs are notoriously difficult to secure against exploits. I think I know what I'm doing, but just in case I'm not going to disclose the details right now. The modified code makes it possible to run FastCGI applications (as well as regular CGI scripts) directly from a shared ScriptAliased directory.

I still need to measure performance under different loads, but so far my FastCGI setup works as expected. After much experimentation, I'm also beginning to better understand the interaction between the web server and FastCGI applications, which takes some getting used to. I'll have more to say about this soon.

29 April, 2005