Notice: This article was written when  SuPHP was the most popular choice for running multiple PHP sites on the same Apache server. Now, php-fpm with different users is a better choice. But still, if you need this article, here it is...

In short, SuPHP runs the PHP web scripts as a  predefined system user, respecting all system file privileges and ownership rights. If that user cannot read / execute / write to a file then its corresponding website can do no harm to other websites on the same server. This is essential for website security because very often a compromised site on a server spreads malware across the rest. This is especially true for shared webhosting.

While there are many advanced ways for restricting scripts such as those provided in Litespeed or even running chrooted Apache the current setup will prove to be absolutely perfect for our needs, i.e. not to allow a script from one site even to read (not to mention write / execute) scripts from another site.

Before continuing we should mention that using SuPHP might be slower then using Apache PHP module. This is the main drawback of this otherwise great solution for website security.

We will be compiling the latest SuPHP from source on a Centos 5.5 Linux server. First we make sure that the following packets are installed:

# yum php-cli httpd-devel apr apr-devel gcc-c++ ncurses-devel

Especially php-cli is important since it will provide us with php-cgi binary (/usr/bin/php-cgi) which processes all requests from SuPHP.

After that we get the latest (currently version 0.7.1) source for SuPHP from here and extract it in a test directory. After that we go into this directory and begin compiling from source:

# ./configure --prefix=/usr --sysconfdir=/etc --with-apr=/usr/bin/apr-1-config --with-apxs=/usr/sbin/apxs --with-apache-user=apache --with-setid-mode=paranoid --with-php=/usr/bin/php-cgi --with-logfile=/var/log/httpd/suphp_log --enable-SUPHP_USE_USERGROUP=yes

Finally we run 'make && make install' when there are hopefully no errors. To confirm everything has gone fine you should end up with a new file in:

/etc/httpd/modules/mod_suphp.so

Next, we remove the default php handlers. For this purpose make sure that you have commented in the main configuration file and /etc/httpd/conf.d/php.conf:

#LoadModule php5_module modules/libphp5.so

Instead, we create a new conf file in /etc/httpd/conf.d/suphp.conf which contains:

LoadModule suphp_module modules/mod_suphp.so
AddHandler x-httpd-php .php .php3 .php4 .php5 .phtml
suPHP_AddHandler x-httpd-php
suPHP_Engine on
suPHP_ConfigPath /etc/

Next, step is to create the SuPHP core configuration file /etc/suphp.conf:

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

;Loglevel
loglevel=info

;User Apache is running as
webserver_user=apache

;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=true

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

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

; Minimum UID
min_uid=200

; Minimum GID
min_gid=200

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

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

All of the above options are very important. For example, min_uid and min_gid should be in accordance with your users uid and gid. If your web users begin with 500 then the above is optimal making sure that root(uid and gid 0) files are not executed.

Also, very important is to put quotes around php:/usr/bin/php-cgi. Otherwise you will end up with a similar error in your apache error log(/etc/httpd/logs/error_log):

[Thu Apr 28 17:05:58 2011] [error] [client 192.168.0.223] SecurityException in Application.cpp:511: Unknown Interpreter: php
[Thu Apr 28 17:05:58 2011] [error] [client 192.168.0.223] Premature end of script headers: opa.php

The last thing is to create the proper vhosts. Here are 2 samples:

<VirtualHost *:80>
ServerName  example.org
DocumentRoot /var/www/test
suPHP_UserGroup testtt testtt
</VirtualHost>
<VirtualHost *:80>
ServerName  website-security.info
DocumentRoot /var/www/test2
suPHP_UserGroup test2 test2
</VirtualHost>

Just make sure that:

  1. The specified users in the vhosts exist (run 'adduser test2').
  2. That the DocumentRoot directories are owned by these users (run 'chown test2: /var/www/test2)

To check it try creating a test php file (e.g. /var/www/test/test.php) in one of the sites with permissions 400, i.e. the owner can only read it. This page will run correctly as you can easily find out by opening it in your browser. Then try to open it with a php file from the other site using something as simple as echo system('/bin/cat /var/www/test/opa.php'). You will see an empty page, i.e. you cannot read it.

Using SuPHP was the most popular and reliable solution for isolating sites from one another and taking best care for your websites security.