I was able to spawn a separate PHP FastCGI server with children and have Apache connect to it. It was trickier than I thought. The big benefit is that one shared APC cache can serve all the PHP child processes and being able to use a multithreaded Apache without worrying about whether my PHP dependencies are thread safe.

The trick isn't getting it working, but getting it working the way I wanted. I want ".php" files to be processed by the FastCGI server and have the other files sent by Apache. Without some tricky configuration, Apache's mod_fastcgi can only send specified file requests or specified directories--plus all their contents--to the external FastCGI server.

But I am getting ahead of myself. Let me back up to my old lighttpd setup: I had lighttpd installed, and a script that launced several php-cgi processes and listened on a network socket. Lighttpd would connect to the php-cgi processes and let them handle PHP processing. Apache can do this, too, but it was hard for me to easily find out how online.

As it turns out, the spawn-fcgi program from lighttpd that I used to start the FastCGI server is now a project on its own. Supposedly the mod_fastcgi developers have a launcher program, too, but I couldn't easily find it, and I was already familiar with spawn-fcgi and was happy to see it's being maintained. I downloaded the source package from the site, extracted it and then did the usual "./compile", "make" and "sudo make install". So now I have /usr/local/bin/spawn-fcgi installed.

There is some good info on lighttpd's ModFastCGI documentation site on launching a PHP server with spawn-fcgi and various helper scripts. I modified one slightly to make it use a unix socket instead of a network tcp socket:

#!/bin/bash

## ABSOLUTE path to the spawn-fcgi binary
SPAWNFCGI="/usr/local/bin/spawn-fcgi"

## ABSOLUTE path to the PHP binary
FCGIPROGRAM="/usr/bin/php-cgi"

## TCP port to which to bind on localhost
FCGIPORT="1026"

## bind to unix domain socket
FCGISOCKET="/tmp/php.sock"

## number of PHP children to spawn
PHP_FCGI_CHILDREN=4

## maximum number of requests a single PHP process can serve before it is restarted
PHP_FCGI_MAX_REQUESTS=1000

## IP addresses from which PHP should access server connections
FCGI_WEB_SERVER_ADDRS="127.0.0.1"

# allowed environment variables, separated by spaces
ALLOWED_ENV="ORACLE_HOME PATH USER"

## if this script is run as root, switch to the following user
USERID=www-data
GROUPID=www-data

################## no config below this line

if test x$PHP_FCGI_CHILDREN = x; then
  PHP_FCGI_CHILDREN=5
fi

export PHP_FCGI_MAX_REQUESTS
export FCGI_WEB_SERVER_ADDRS

ALLOWED_ENV="$ALLOWED_ENV PHP_FCGI_MAX_REQUESTS FCGI_WEB_SERVER_ADDRS"

### This if-then-else is for opening a network TCP port
#if test x$UID = x0; then
#  EX="$SPAWNFCGI -n -p $FCGIPORT -f $FCGIPROGRAM -u $USERID -g $GROUPID -C $PHP_FCGI_CHILDREN"
#else
#  EX="$SPAWNFCGI -n -p $FCGIPORT -f $FCGIPROGRAM -C $PHP_FCGI_CHILDREN"
#fi

### This if-then-else is for opening a unix socket
if test x$UID = x0; then
  EX="$SPAWNFCGI -n -s $FCGISOCKET -f $FCGIPROGRAM -u $USERID -g $GROUPID -C $PHP_FCGI_CHILDREN"
else
  EX="$SPAWNFCGI -n -s $FCGISOCKET -f $FCGIPROGRAM -C $PHP_FCGI_CHILDREN"
fi

# copy the allowed environment variables
E=

for i in $ALLOWED_ENV; do
  E="$E $i=${!i}"
done

# clean the environment and set up a new one
exec env - $E $EX

In the above script I had to use /bin/bash instead of Ubuntu's default /bin/sh as it uses some of bash's features. Also note that with spawn-fcgi you can have a network tcp socket or a unix socket, but not both. On my test server I just simply ran the above script as root; it won't restart itself if the VPS is restarted or if the script crashes. I have daemontools on my real server, and I'll use that to start and monitor the launcher script. The link to lighttpd's site has other startup scripts worht looking at.

You can't use mod_fcgid to connect to the externally spawned FastCGI process. It can only launch and manage the processes itself. So I loaded mod_fastcgi and used the FastCgiExternalServer directive:

<IfModule mod_fastcgi.c>
  FastCgiExternalServer /srv/www/site/fcgi -socket /tmp/php.sock
</IfModule>

That tells Apache that any request under the /srv/www/site/fcgi directory gets passed to the FastCGI process with a unix socket at /tmp/php.sock. Unfortunately there is not an simple configuration to have it just run php files, and the FastCGI server may not know what to do with static files like pictures or .css files.

There is a good article explaining the FastCgiExternalServer directive. Its solution to having just the .php files be handled by the external server involve adding a handler, assigning an action to the handler pointing to a nonexistent script and then aliasing the nonexistent script back to a folder symlinked to the original directory. The only way I could find to simplify that was to use a ReWriteRule. In either case we need to unfortunately modify the configuration for each vhost to make it work.

I have several vhosts under /srv/www/. Following the articles example I created a symlink /srv/fcgi pointing to /srv/www . Then I modified my mod_fastcgi configuration as such:

<IfModule mod_fastcgi.c>
  FastCgiExternalServer /srv/fcgi -socket /tmp/php.sock
  ReWriteEngine On
  ReWriteCond %{DOCUMENT_ROOT} ^/srv/www/(.*)
  ReWriteRule ^/(.*\.php(3|4)?(\?.*)?)$ /srv/fcgi/%1/$1
</IfModule>

Now the external FastCGI server is invoked whenever a file under /srv/fcgi is accessed, but /srv/fcgi is just a symlink to /srv/www. Instead of the above article's gyrations I figured out the above rewrite rules that will rewrite any request for a .php file to /srv/fcgi/(rest-of-document-root)/(request_URL) . So in effect the rewrite points back to the original file, but through a symlink that makes Apache use the FastCGI server to process it. The ReWriteCond shown doesn't actually make a decision; it is giving me a reference to use when constructing my rewritten path name.

Now I have to modify my vhosts. Rewrite rules don't carry over to vhosts by default. For each VirtualHost section I have to add the following which allows the server rewrite rules inherit to the vhost:

ReWriteEngine On
ReWriteOptions Inherit

Alternately I could just put the rewrite rules in each VirtualHost section. In fact I may need to if I have other rewrite rules for pretty URLs.

With FastCGI--whether externally spawned or managed by mod_fcgid or mod_fastcgi--you also need ExecCGI enabled in the Options directive.

I used Apache benchmark and verified that all the child proceses are being used concurrently. And now the APC cache is shared among all the child processes.