Apache Virtual Hosts with FastCGI and suEXEC

This setup is typically used in shared web hosting environments. Why this method rather than using mod_php, well quite simply, it allows php, perl or even python code to be executed as the user which owns it which means that the user will not be able to access user space that their user does not have permission to. It also offer the ability to do true process accounting and track which site is using the most resources on a host. It also offers a php.ini file for each site hosted, as well as on the fly changes to said file because php calls it as scripts are executed. This type of setup would not normally be used on a server hosting a single web page although there could be advantages to doing so, but I will not go into these. Note: This guide also assumes that you know how to setup virtual hosts the normal way using mod_php.

Installation

The following packages will need to be installed. (These are what they are called on Debian, RedHat etc might be different)

  • apache2
  • libapache2-mod-fastcgi
  • apache2-suexec

Virtual Hosts

Create the required directory structure and then set up your virtual hosts as normal

<VirtualHost *:80>
    ServerName linux-101.org
    DocumentRoot /var/www/linux-101/webroot
    ServerAdmin hostmaster@linux-101.org
 
    ErrorLog /var/log/apache2/linux-101.org_error.log
    CustomLog /var/log/apache2/linux-101.org_access.log combined
 
    <Directory "/var/www/linux-101/webroot">
        Options Indexes Includes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
 
</VirtualHost>

Restart apache and test.

All working (without PHP, because we're not using mod_php and fastCGI is not ready yet)? Good.

FastCGI

Now assuming that your web directory is /var/www/linux-101/webroot create a new directory /var/www/linux-101/.cgi-bin and place the following script inside:

php5.fcgi

   #!/bin/bash
   #
   # php5.fcgi
   # Shell Script to run PHP5 using mod_fastcgi under Apache 2.x
   #
   #USER=$(/usr/bin/whoami)
   #PHPRC="/var/www/$USER/.cgi-bin/php.ini"
   PHP_FCGI_CHILDREN=5
   #PHP_FCGI_MAX_REQUESTS=1000
   #export PHPRC
   export PHP_FCGI_CHILDREN
   #export PHP_FCGI_MAX_REQUESTS
   exec /usr/bin/php5-cgi

Make this shell script executable

# chmod 755 php5.fcgi

Now, modify your virtual host to look like this:

Note: Apache actions module needs to be enabled, and mod_php disabled.

<VirtualHost *:80>
    ServerName linux-101.org
    DocumentRoot /var/www/linux-101/webroot
    ServerAdmin hostmaster@linux-101.org
 
    ErrorLog /var/log/apache2/linux-101.org_error.log
    CustomLog /var/log/apache2/linux-101.org_access.log combined
 
    ScriptAlias /cgi-bin/ "/var/www/linux-101/.cgi-bin/"
 
    <Directory /var/www/linux-101/.cgi-bin/>
        AllowOverride None
        Options None
        Order allow,deny
        Allow from all
    </Directory>
 
    <Directory "/var/www/linux-101/webroot">
        Options Indexes Includes FollowSymLinks ExecCGI
        AllowOverride All
        AddHandler php5-fastcgi .php .php5 .php4
        Action php5-fastcgi /cgi-bin/php5.fcgi
        Order allow,deny
        Allow from All
    </Directory>
 
</VirtualHost>

Note the line ScriptAlias /cgi-bin/ "/var/www/linux-101/.cgi-bin/", the cgi-bin directory directive and the extra options to the webroot directive.

Create the file /var/www/linux-101/webroot/phptest.php

<?PHP
  phpinfo();
?>

Now browse to it and you should see a long page with all the PHP environment variables.

suEXEC

Till now, all your php scripts have been running as the www-data (Debian) user, which means that the script will have access to anything that the apache user has access to. On a shared server, it means that any user is able to access (be it read or write) data in another user's web directory because apache has access. This is where suEXEC comes in. It forces our php apps to run as the user who owns them. Note: suEXEC has been compiled with the default docroot, which is normally /var/www and therefore will not work for files outside of this path.

So, create a new user for your first virtual host. (Mine is called linux-101)

# useradd -d /var/www/linux-101 linux-101

Now copy /etc/php5/cgi/php.ini to /var/www/linux-101/.cgi-bin/

# cp -fv /etc/php5/cgi/php.ini /var/www/linux-101/.cgi-bin/

Now that you have a site specific php.ini file, edit it and set open_basedir

; open_basedir, if set, limits all file operations to the defined directory
open_basedir = /var/www/linux-101

Next, modify php5.fcgi to look like this:

   #!/bin/bash
   #
   # php5.fcgi
   # Shell Script to run PHP5 using mod_fastcgi under Apache 2.x
   #
   USER=$(/usr/bin/whoami)
   PHPRC="/var/www/$USER/.cgi-bin/php.ini"
   PHP_FCGI_CHILDREN=5
   #PHP_FCGI_MAX_REQUESTS=1000
   export PHPRC
   export PHP_FCGI_CHILDREN
   #export PHP_FCGI_MAX_REQUESTS
   exec /usr/bin/php5-cgi

We now need to make sure that linux-101 owns the dir and all the files below it.

# chown -Rf linux-101:linux-101 /var/www/linux-101
# chown -Rf linux-101:linux-101 /var/www/linux-101/.cgi-bin

And if you don't want your users changing php.ini to suite their own needs

# chown root:root /var/www/linux-101/.cgi-bin/php.ini

Set permissions on the parent dir so no one else can get in.

# chmod 710 /var/www/linux-101

And then make sure apache still is able to open the parent directory.

# chown linux-101:www-data /var/www/linux-101

Lastly, add the following important line to your virtual host configuration:

SuexecUserGroup linux-101 linux-101

This tells apache which user and group to use when executing files/apps.

Your final virtual host config should look something like this:

<VirtualHost *:80>
    ServerName linux-101.org
    DocumentRoot /var/www/linux-101/webroot
    ServerAdmin hostmaster@linux-101.org
 
    ErrorLog /var/log/apache2/linux-101.org_error.log
    CustomLog /var/log/apache2/linux-101.org_access.log combined
 
    SuexecUserGroup linux-101 linux-101
    ScriptAlias /cgi-bin/ "/var/www/linux-101/.cgi-bin/"
 
    <Directory /var/www/linux-101/.cgi-bin/>
        AllowOverride None
        Options None
        Order allow,deny
        Allow from all
    </Directory>
 
    <Directory "/var/www/linux-101/webroot">
        Options Indexes Includes FollowSymLinks ExecCGI
        AllowOverride All
        AddHandler php5-fastcgi .php .php5 .php4
        Action php5-fastcgi /cgi-bin/php5.fcgi
        Order allow,deny
        Allow from All
    </Directory>
</VirtualHost>

Now, when you browse to phptest.php, the php processing should be done by the user, and not as www-data or whatever your apache user is called.

To test, run

# ab -c 100 -n 10000 http://example.com/

against your site. This will generate 10,000 (100 simultaneously) requests to your site and by running top you should see your top process be php5-cgi owned by the user who owns that site.

Repeat these steps for each virtual host, substituting linux-101 for your own sites.

Burtronix Banner W3C Banner