How To Setup SuPHP with Apache2

This HOWTO will show you how to:

The configuration will let you set the file system permissions to prevent cross-site read access, while still allowing PHP scripts to run as their respective users, and the apache (www-data) user to access static content.

You need to be sure you using the prefork apache:

aptitude install apache2-mpm-prefork

Install suPHP and PHP5-CGI:

aptitude install libapache2-mod-suphp php5-cgi

Configure suPHP:

[global]
;Path to logfile
logfile=/var/log/suphp/suphp.log

;Loglevel
loglevel=info

;User Apache is running as
webserver_user=www-data

;Path all scripts have to be in
docroot=/var/www

;Path to chroot() to before executing script
;chroot=/mychroot

; Security options
allow_file_group_writeable=false
allow_file_others_writeable=false
allow_directory_group_writeable=false
allow_directory_others_writeable=false

;Check wheter script is within DOCUMENT_ROOT
check_vhost_docroot=true

;Send minor error messages to browser
errors_to_browser=false

;PATH environment variable
env_path=/bin:/usr/bin

;Umask to set, specify in octal notation
umask=0073

; Minimum UID
min_uid=2000

; Minimum GID
min_gid=2000

[handlers]
;Handler for php-scripts
x-httpd-php=php:/usr/bin/php-cgi

;Handler for CGI-scripts
x-suphp-cgi=execute:!self

Before enabling the module, comment out the lines in /etc/apache2/mods-available/suphp.conf as shown below so it's not enabled for all virtual hosts:

<IfModule mod_suphp.c>
#       DISABLED IN FAVOUR OF PER-VIRTUAL-HOST ENABLE
#       AddHandler x-httpd-php .php .php3 .php4 .php5 .phtml
#       suPHP_AddHandler x-httpd-php
#       suPHP_Engine on
#       THIS WAS ALREADY DISABLED
# # Use a specific php config file (a dir which contains a php.ini file)
#       suPHP_ConfigPath /etc/php4/cgi/suphp/
# # Tells mod_suphp NOT to handle requests with the type <mime-type>.
#       suPHP_RemoveHandler <mime-type>
</IfModule>

Then do the same for mod_php in /etc/apache2/mods-available/php5.conf:

<IfModule mod_php5.c>
#  *** Disabled in favour of enabling on per-site basis ***
#  AddType application/x-httpd-php .php .phtml .php3
#  AddType application/x-httpd-php-source .phps
</IfModule>

Then enable the apache module:

a2enmod suphp

Then configure a virtual host as shown below, where you comment out either the suPHP or mod_php sections as required:

<VirtualHost 127.0.0.1:80>

        ServerName www.example.com
        DocumentRoot /var/www/example.com/www

        ################
        # ENABLE SUPHP #
        ################
        AddHandler x-httpd-php .php .php3 .php4 .php5 .phtml
        suPHP_AddHandler x-httpd-php
        suPHP_Engine on
        suPHP_ConfigPath /var/www/example.com/etc/
        # suPHP_UserGroup example.com virtual

        ###################
        # ENABLE MOD-PHP4 #
        ###################
        # AddType application/x-httpd-php .php
        # AddType application/x-httpd-php-source .phps

        ...etc

</VirtualHost>

Note that the suPHP_UserGroup is not supported in the Debian package since it is compiled without the PARANOID security setting. I have tested compiling from source with that enabled and it works just fine, but I prefer the Debian way of doing things.

You need a real UNIX user for each virtual host that will run under suPHP. You also need to make all of those users a member of a new group called virtual. Then set permissions on suPHP enabled virtual hosts as shown below:

ls -la /var/www
dr-x---r-x 9 example.com virtual 4.0K 2007-08-23 09:43 example.com
ls -la /var/www/example.com
dr-x------ 2 example.com virtual 4.0K 2007-08-23 09:27 etc
drwx------ 2 example.com virtual 4.0K 2007-08-23 09:42 logs
drwx------ 3 example.com virtual 4.0K 2007-08-23 09:32 mail
drwx------ 2 example.com virtual 4.0K 2007-03-30 14:13 tmp
drwx---r-x 3 example.com virtual 4.0K 2007-08-23 08:42 www
ls -la /var/www/example.com/www
-rw----r-- 1 example.com virtual   12 2007-03-30 14:23 index.htm
-rw----r-- 1 example.com virtual  154 2007-08-23 09:51 whoami.php
ls -la /var/www/example.com/etc
-r-------- 1 example.com virtual   12 2007-03-30 14:23 php.ini

Our domain user owns all their files and has read/write/execute access on their directories and read/write access on their files. PHP scripts will be executed as that domain user. It's also simple to setup FTP and MAIL to use that user.

The group setting explicitly denies access to other virtual users, while the apache user can continue to access files as www-data through the world-readable permission. This arrangement is necessary since suPHP is only used for PHP scripts; static content is still accessed as www-data.

Why not just make the files group readable instead of world readable? That's because if all virtual users use the same group then virtual users could read other virtual users files though PHP. If you have each virtual host it's own group (as well as user) then you would need to make www-data a member of each group and would quickly hit the limit that a user can only be a member of a maximum of 32 groups.

These permissions allow our user to execute PHP, access files over FTP and have mail delivered as their own user, while disallowing read access to other virtual users, while still allowing www-data read access for static content.

Note that there is also a per-site php.ini file which the user can read but not write to.

Note that this permissions model is only secure on a server that is used purely as a virtual hosting server. There must be no other users on the server that are not a member of the virtual group or they will be able to read other virtual user's files. There should certainly be no user shell access.

Restart Apache:

/etc/init.d/apache2 restart

If you get a 500 internal server error when accessing a suPHP enabled site, and a premature end of script headers error in error.log, then you should check that file's permissions. There may be a clue in /var/log/suphp/suphp.log. Note that group or world writable files will not be executed by suPHP.

One final note: take care using suPHP along side mod_php; it may introduce serious security holes. If possible use only suPHP and disable mod_php as shown below:

a2dismod php5
/etc/init.d/apache2 restart