Secure PHP environments with PHP, suexec and FastCGI (mod fcgid)

You want to have a secure setup for your PHP web applications but don't want to compromise performance too much. This page is about a setup which uses FastCGI (mod_fcgid) and suexec.

I found several tutorials on the web but there were always some problems. Some used self-compiled versions of suexec with some security checks disabled (debianhowto.de). Most of them uses mod_fastcgi which seems to be a dead end these days (not updated for a long time despite problem reports floating around, problematic license). None of them explained everything...

  1. Install mod_fcgid. I won't go into much detail here. Fedora has this package in extras. For RHEL 4, you can just grab the Fedora source rpm and recompile it (without SElinux).
  2. Install a FastCGI enabled PHP CGI binary. PHP supports this as of 4.2. I did not find a wrapper script for PHP (Jan 2007) so you have to compile that binary yourself or get it from your distribution. The configure switch is "--enable-fastcgi". To check if your binary has FastCGI support, call "php -v". "PHP 4.3.9 (cgi)" has no FastCGI support, "PHP 4.3.9 (cgi-fcgi)" has. I made some packages which can be used on RHEL 4 (and probably Fedora), see below. My FastCGI PHP binary is in /usr/bin/php4-fcgi.
  3. You need a wrapper (shell) script which is started by suexec. The main purpose of this script is that the php binary can be installed in the standard locations such as /usr/bin. suexec checks that the executed program is in the docroot (/var/www by default). Furthermore you can use this script to set custom environment variables (suexec is a bit too restrictive sometimes) and flags for the PHP interpreter so that a user can have its own PHP configuration. I named this wrapper "php<version>.fcgi" and put it in /var/www/username/sitename/bin.
  4. After that, you have to enable the FastCGI in the Apache (vhost) configuration. I chose to use it for all PHP scripts in the html directory (my document root) but you can easily restrict this to subdirectories or certain files:
        <Directory /var/www/username/sitename/html>
            AddHandler fcgid-script .php
            FCGIWrapper /var/www/username/sitename/bin/php4.fcgi .php
            Options +ExecCGI
        </Directory>
    
  5. For debugging, look into the suexec.log. Use "LogLevel DEBUG" for Apache so more errors are reported.

Wrapper Script

Filename: /var/www/username/sitename/bin/php4.fcgi

#!bash
#!/bin/sh
export PHPRC="/etc/php.ini"
export PHP_FCGI_CHILDREN=4
exec /usr/bin/php4-fcgi

FastCGI enabled PHP on RHEL 4

Of course you can just grab the PHP so‏urces and compile them. You only need the php binary in the cgi directory. But I wanted to use RHEL's PHP version (with all Redhat patches!) and RPMs so that the FastCGI binary can be deployed as every other custom software I do install.

Therefore I modified the SPEC file slightly. A patch for the php spec file and the modified spec file itself are on my webserver.

To compile a FastCGI enabled PHP, grab the RHEL/CentOS PHP source RPM and install it with rpm -i. After that, patch (or replace) the spec file and issue a rpmbuild -ba SPECS/php.spec. After the compile (which takes some time even on my AMD 64 X2 4200+), just save the php-fcgi rpm. All other RPMs can be deleted.

Long running scripts

Although the issue is not closely related to the actual installation of mod_fcgid, I don't have a better place for it now. You may experience some inexplicable "500 - Internal Server Error" pages when running perfectly fine scripts. In the Apache error log you find things like:

mod_fcgid: read data timeout in 20 seconds
[error] [client ip] Premature end of script headers: index.php, referer: ...
mod_fcgid: process <script>(pid) exit(communication error), get stop signal 15

This is because the script timeout (IPCCommTimeout) is set to 20 seconds by default. If your script does some more cpu-intensive computations or your server is under heavy load, this may be to short. Therefore, I suggest setting the mod_fcgid directives IPCConnectTimeout and IPCCommTimeout to 10/60 seconds. This blog entry at 1stein.org was very helpful explaining the error.