Introduction
suPHP is an Apache module primarily written in C++, who’s purpose is to execute PHP code as a different user to the web server. This is desirable when running WordPress or Joomla who’s files have been uploaded as a chroot’d FTP
/SFTP user.
To understand how suPHP works, we must first understand how Apache modules work and the differences between this and using the CGI API.
Differences between running code as a module and CGI
An Apache module
An Apache module is an application extension (.so file on Linux, .dll on Windows). When this is loaded by a binary executable such as Apache, it then has access to the public functions, variables and constants contained within. In the case of Apache with mod_php, we can tell it to run the code in the PHP extension by using the AddHandler/SetHandler directive. This is executed by the ap_invoke_handler() function in the Apache runtime.
Because it essentially functions as one big executable, Apache is then able to recognise PHP configuration variables such as “php_value register_globals 0” inside .htaccess files and Apache virtual host containers.
The CGI/FastCGI API
For Apache to run external applications such as Perl or Python, it uses another module called mod_cgi. This contains functions which pass the entire file to an external application such as /usr/bin/perl. It’s essentially the same as running “/usr/bin/perl ” on the command line, which is why you have to output HTTP headers such “Content-myscript.pl Type: text/html\n\n” in Perl if you know it will be sent over HTTP for example. A CGI script can be written in any language, the most common of which are Perl, Python, PHP and even C. If it runs on the command line, it can run via the CGI API.
Where does suPHP come into this?
In the Apache module example, PHP code will be executed as the Apache user and will appear to be running as “httpd” or “apache2” because it runs *inside* the calling process.
What we want to accomplish is running PHP code as the same user that uploaded the files. If we can do that then several very useful scenarios become apparent:PHP and the user become visible in tools such as top and ps which is invaluable for troubleshooting on a server hosting multiple domains.
- As long as the permissions are set to 755 for directories and 644 for files, then PHP will be able to write to directories such as /uploads and Apache will be able to enter the directories and read the static content too, thanks to the read/execute permissions for all.
- Only one user needs to own the files. ACLs are no longer required, or desirable.
- PHP code cannot be run as root by default
- Users have privilege separation when running PHP code
So how does this work?
As we’ve seen previously, Apache can run external programs via CGI. This is the same as what happens when we use suPHP, except that there’s an Apache module involved. mod_suphp “pretends” to be PHP essentially, but really acts as a middleman between Apache and the PHP binary. When a request is made for a PHP script, Apache will send it to mod_suphp (SetHandler application/x-httpd-suphp .php) which then quickly forks into a new Apache process, “su” to the owner of the file and then executes PHP with the script name and any variables as arguments.
Here’s what the request/response flow looks like, simplified somewhat:
There are some differences that I found between the RHEL variants and Ubuntu.
On RHEL, you have to add a third-party repository (RPMForge) and also use a slightly different syntax when telling Apache how to use the suPHP module.
RHEL also doesn’t run the code as the user of the PHP code by default, you have to specify it inside the virtual host configuration. Configuration is also more time consuming because the RPMForge version has several bugs that need to be corrected before restarting Apache.
On Ubuntu, it’s simply a matter of installing the module because it’s already provided in the vendor repo and then configure it. The bugs in RHEL ar e not present in this version.
In addition, Ubuntu doesn’t seem to recognise the suPHP_UserGroup directive (more research is needed here), but instead runs the PHP code as the owner of the file by default.
Caution: A bug has been noticed on Ubuntu 13.04 servers where Apache may keep child processes may be left running as the “root” user. This needs more investigation.
Requirements
- RHEL/Centos 5 or 6 with Apache2, PHP5 and the RPMForge repo.
- Ubuntu 12.04 or above with Apache2 and PHP5. Ubuntu 10.04 is untested but may work.
- Do not install on a server running Plesk, it’s untested and will almost certainly break things. In Plesk, choose FastCGI in the GUI for each domain instead of mod_php.
Pre-installation notes
We will install suPHP in a manner that will only run a certain virtual host in this way, and leave the others to run as the Apache PHP module. This is desirable because it means we can enable it on a site-by-site basis. This is particularly good for testing it without affecting the main site(s).
Pre-flight checks
Check the permissions on the sessions directory specified in the php.ini configuration file. The domain/FTP user that will be executing the scripts must have full access. Consider creating a special group such as “phpsessions”, add the Apache user and the domain user into this and “chgrp” the session folder to this new group.
PHP configuration directives in virtual hosts and .htaccess files are no longer available e.g. “php_admin_value register_globals on”. Apache won’t recognise them because it’s no longer running PHP built-in. Instead, you must tell suPHP to load site-by-site PHP values like this:
suPHP_ConfigPath /var/www/vhosts/example.com/public_html/.php.ini
Then, you can set them in that file in the usual php.ini format: “register_globals On”
Installation
Ubuntu 12.04 and above
Update the package list and install the module we need.
*** WARNING – Apache will automatically reload the configuration at this point ***
# apt-get update && apt-get install libapache2-mod-suphp
Edit “/etc/apache2/mods-enabled/suphp.conf” and comment out everything in this file.
If there is a virtual host (the live sites etc) that we want to be still running on the Apache PHP module, we should reload now so it reverts back to using it:
# service apache reload
If there are any virtual hosts that should be using the Apache2 module as above then leave them as is, no configuration changes are needed.
Edit the virtual host configuration file that needs to run as suPHP and enter the following lines inside the <VirtualHost> tag but after the “ServerName /Alias” section:
php_admin_flag engine off
suPHP_Engine on
<FilesMatch “\.ph(p3?|tml)$”>
SetHandler application/x-httpd-suphp
</FilesMatch>
suPHP_AddHandler application/x-httpd-suphp
Go to the root of the virtual host’s public_html area (e.g. /var/www/vhosts/example.com/public_html/) and run these commands to fix the permissions.
The username/group should be the same as the user who uploads the files via FTP/SFTP:
*** WARNING – this change can be dangerous, be careful ***
# chown username:group * -R
# find . -type f -exec chmod 644 {} \;
# find . -type d -exec chmod 755 {} \;
Test the configuration and perform a graceful restart:
# apachectl configtest
# service apache2 graceful
RHEL/Centos 5 and 6
This is based on the following: http://chrisam.net/blog/2009/10/11/installing-and-configuring-suphp-on-centos-5-3/
Install the RPMForge repo: http://wiki.centos.org/AdditionalResources/Repositories/RPMForge
After the repo is installed and enabled, go ahead and install the module:
# yum install mod_suphp
Now we edit the file “/etc/suphp.conf”.
Ensure that the line “x-httpd-php= php:/usr/bin/php” has quotes around the binary and also set it to use php-cgi like this: x-httpd-php= php:/usr/bin/php-cgi
Do the same with the line “x-suphp-cgi=execute:!self”:
x-suphp-cgi=”execute:!self”
Finally, change the umask setting to 0022 instead of 0077.
Comment out everything in the file “/etc/httpd/conf.d/suphp.conf” except the LoadModule directive.
Edit the virtual host configuration file and enter the following lines inside the <VirtualHost> tag but after the “ServerName/Alias” section:
suPHP_Engine on
suPHP_UserGroup username group
AddHandler x-httpd-php .php .php3 .php4 .php5
suPHP_AddHandler x-httpd-php
The username/group here should be the same as the file owner/FTP user.
Go to the root of the virtual host’s public_html area (e.g. /var/www/vhosts/example.com/public_html/) and run these commands to fix the permissions.
The username/group should be the same as the user who uploads the files via FTP/SFTP:
*** WARNING – this change can be dangerous, be careful ***
# chown username:group * -R
# find . -type f -exec chmod 644 {} \;
# find . -type d -exec chmod 755 {} \;
Test the configuration and perform a graceful restart:
# apachectl configtest
# service httpd graceful
Testing the installation
The easiest way is to create a file “test.php” with the following contents:
<?php
echo exec(‘/usr/bin/whoami’);
phpinfo();
?>
Chown it to the correct user/group, chmod it to 644, then call it via your web browser. We’re looking for two things: the output of “whoami” should be the same as the owner set in either the virtual host (RHEL) or the script owner (Ubuntu) and the PHP information should show it as running via the FastCGI handler.
You can also use a file like this to verify that the PHP binary is being executed correctly:
<?php
sleep(60);
?>
Call it via your web browser, switch back to the terminal and run “ps aux | grep php” to verify that it’s running as the correct user.
Notes
If the permissions on the site need to be 664 & 775 for any reason, the file “/etc/suphp.conf” (RHEL) or “/etc/suphp/suphp.conf” (Ubuntu) should be edited and the following settings changed to “true” for the group options:
; Security options
allow_file_group_writeable=false
allow_file_others_writeable=false
allow_directory_group_writeable=false
allow_directory_others_writeable=false
In addition, if a PHP file is owned by “root”, you will get an internal server error and it will refuse to execute the file.
This is an important security feature.
If you’re getting Internal Server Error messages, check the /var/log/(apache2|httpd)/(domain-)error_log file and the suphp log file which is found in the same directory.
Common reasons are an incorrect PHP binary specified in the suphp.conf file and incorrect permissions or ownership.
Sessions can cause issues, so check that the directory specified in php.ini is writeable by the user calling the script. See the pre-installation notes previously.
PHP values in Apache config files aren’t recognised and will cause an error 500. See the pre-installation notes.
suPHP has not been tested on a Plesk install, nor on Webmin.
It’s unlikely to work and probably quite dangerous to try this on a server running Plesk, but Webmin might be ok with some tweaking.
suPHP is slightly slower than running as an Apache module, and as running directly via FastCGI.
This is because there is an additional layer of processing that needs to be done in addition to the overhead of switching users.
=== Further information ===
http://docs.joomla.org/Security_Checklist/Hosting_and_Server_Setup
http://www.suphp.org/Documentation.html
Not a Recommended MC configuration
Due to issues with APC and the load it puts on servers (due to running in cgi mode) MC recommends against running PHP via suPHP.
Purpose of this document
To have a singular method of installing and configuring suPHP for customers. Configuring mod_php and suphp on the same box. (Not recommended)
CentOS
yum install mod_suphp
mv /etc/httpd/conf.d/php.conf /etc/httpd/conf.d/php.conf.disabled
touch /etc/httpd/conf.d/php.conf (this is done so that future php updates don’t auto-reenable mod_php)
If mod_suphp is not found in installed repos, you can get it from DAG [unsupported 3rd party repo]
Install DAG – get correct link for the distro / arch
http://dag.wieers.com/rpm/FAQ.php#B
##example: rpm -Uvh http://apt.sw.be/redhat/el6/en/x86_64/rpmforge/RPMS//rpmforge-release-0.5.3-1.el6.rf.
x86_64.rpm
IMPORTANT add includepkgs to the repo or DAG will install a ton of unwanted packages
/etc/yum.repos.d/rpmforge.repo
[rpmforge]
…
enabled = 1
includepkgs = mod_suphp
proceed with yum install mod_suphp above
Edit /etc/httpd/conf.d/mod_suphp.conf
vim /etc/httpd/conf.d/mod_suphp.conf
# This is the Apache server configuration file providing suPHP support.
# It contains the configuration directives to instruct the server how to
# serve php pages while switching to the user context before rendering.
LoadModule suphp_module modules/mod_suphp.so
Uncomment to activate mod_suphp
AddHandler x-httpd-php .php .php3 .php4 .php5
suPHP_AddHandler x-httpd-php
# This option tells mod_suphp if a PHP-script requested on this server (or
# VirtualHost) should be run with the PHP-interpreter or returned to the
# browser “as it is”.
suPHP_Engine on
# This option tells mod_suphp which path to pass on to the PHP-interpreter
# (by setting the PHPRC environment variable).
# Do \*NOT\* refer to a file but to the directory the file resides in.
# E.g.: If you want to use “/path/to/server/config/php.ini”, use “suPHP_Config
# /path/to/server/config”.
# If you don’t use this option, PHP will use its compiled in default path.
suPHP_ConfigPath /etc
Edit suphp.conf
vim /etc/suphp.conf
[global]
;Path to logfile
logfile=/var/log/suphp.log
;Loglevel
loglevel=info
;User Apache is running as
webserver_user=apache
;Path all scripts have to be in
docroot=/
;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 whether 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=0022
; Minimum UID
min_uid=500
; Minimum GID
min_gid=500
; Use correct permissions for mod_userdir sites
handle_userdir=true
[handlers]
;Handler for php-scripts
x-httpd-php=php:/usr/bin/php-cgi
;Handler for CGI-scripts
x-suphp-cgi=execute:!self
Restart Apache
service httpd configtest
service httpd restart
Ubuntu
apt-get install suphp-common libapache2-mod-suphp
a2dismod php5
Edit the /etc/apache2/mods-available/suphp.conf file
vim /etc/apache2/mods-available/suphp.conf
<IfModule mod_suphp.c>
AddType application/x-httpd-suphp .php .php3 .php4 .php5 .phtml
suPHP_AddHandler application/x-httpd-suphp
<Directory />
suPHP_Engine on
</Directory>
# By default, disable suPHP for debian packaged web applications as files
# are owned by root and cannot be executed by suPHP because of min_uid.
<Directory /usr/share>
suPHP_Engine off
</Directory>
# # 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>
vim /etc/suphp/suphp.conf
Edit the /etc/suphp/suphp.conf file
[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:${HOME}/public_html
;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=0022
; Minimum UID
min_uid=1000
; Minimum GID
min_gid=1000
[handlers]
;Handler for php-scripts
application/x-httpd-suphp=”php:/usr/bin/php-cgi”
;Handler for CGI-scripts
x-suphp-cgi=”execute:!self”
Restart Apache
service apache2 restart
Gotchas
Make sure each virtual host has files owned by different users. Make sure that Apache has the same user listed in the User and Group directive; if not, you will get errors in error log indicating it didn’t have sufficient permission for making a process /usr/bin/suphp for that page. This is likely because suphp has only one directive specifying ‘webserver_user’ and doens’t know how to recognize the request when it comes from another group.
You also have to make sure that the each document root has a parent directory either owned by that same user or root. If this is not the case, you will get an error ‘parent directory not owned by root.’
phpMyAdmin as well as some other apps write to /var/lib/php/session. Permissions won’t allow other users to write so you may need to update the session save path to tmp.
php_admin_value flags no longer work in .htaccess. WordPress, Joomla, and Drupal do not contain these values as of this writing but it might come up. Settings need to be in the php.ini. For some applications, you may be able to achieve the same result by using an ini_set value in the main config file. For example, if you wanted to increase max_execution_time in WordPress, add “ini_set(‘max_execution_time’, 1000000000000000000000000)”
to wp-settings.php. You can find a full list of allowed values at
http://www.php.net/manual/en/ini.list.php