This article is about setting up Apache to run PHP as a particular user (suexec), in a virtual hosting environment (vhost) and mitigating the performance hit from doing so (fcgi).
The article is aimed at web developers and webmasters who want to run PHP scripts more securely, do not want to recompile suexec and have sites hosted under user accounts within /home/.
The method shown here allows you to run PHP as an Apache module whilst also running it over CGI.
The article does not cover suphp or mod_fastcgi.
Spare me the web developer back-story, just show me how to get PHP, suexec and mod_fcgi working together.
Why suexec, php and fcgi?
What problem are we trying to solve? Short answer: security + performance. Long answer: When we started as web developers in late 1999 we were using perl and the Common Gateway Interface (CGI) protocol to deliver interactive websites. Apache allowed perl scripts to run as a particular user which meant that permissions for those scripts could be locked down to 700. In other words only that user could access those scripts. Web developers eventually turned to PHP and the most common way to run PHP was as a module (mod_php.so) which meant PHP scripts ran as an unprivileged user – typically www-data.
Consequently, PHP scripts and the directories they wrote to had to have less restrictive security permissions. Directories are normally set to 777 and the scripts themselves 644. This is true of all the well-known open source PHP project like Joomla, WordPress, Drupal, Plogger, OSCommerce, Zencart, Moodle, etc. Not only were directories world writeable but any script containing a password was now world-readable.
With the large scale adoption of Apache virtual hosting there were lots of sites, belonging to lots of users on one machine and their files were now readable by other users.
Lots of web developers would like to run PHP as a particular user, “suexec” with its tighter permissions model but it is not a common setup despite many articles on the subject. There is definitely a performance hit when running PHP with suexec. To overcome this web developers/sys admins also run PHP scripts using the FastCGI protocol; the Apache implementation of which is called mod_fcgid.
Why this article?
Does the web need another article on this? In my experience yes, all the guides I read on the web did not work for me, YMMV. Many guides require you to recompile suexec and this is not necessary. Others used fastcgi and I wanted to use fcgid because I knew and trusted the Apache brand. There are also differences between distros and versions across the same distro. I’ll be using Debian 6.0 (Lenny) for this article and indicating what changes are required for Debian 5.0 (Etch).
Once installed you can enable fcgid & suexec:
a2enmod fcgid ##may be enabled by dflt after install but requires a restart!
AddHandler fcgid-script .php
# AddHandler fcgid-script .fcgi
FcgidConnectTimeout 20 #IPCConnectTimeout on Lenny
FCGIWrapper /usr/bin/php5-cgi .php
# FCGIWrapper /usr/bin/php5-cgi .fcgi
# To re-enable php in user directories comment the following lines
# (from <ifmodule> to </ifmodule>.) Do NOT set it to On as it
# prevents .htaccess files from disabling it.
php_admin_value engine Off
Do a restart and check everything is still ok … at the command line execute:
The above 2 changes needs to be made at the same time, doing fcgid alone will break things!
By installing Debian’s very handy apache2-suexec-custom we do not have to recompile suexec which, by default, wants to run under /var/www. Like most web developers using vhosting we want to run under /home/username. At the command line execute:
which shows where the configuration directory is & where errors will be logged to:
The first line tells us we need to create
(where testsuexec is our username) to look like this, which allows us to run out of /home instead of the default /var/www.
# The first two lines contain the suexec document root and the suexec userdir
# suffix. If one of them is disabled by prepending a # character, suexec will
# refuse the corresponding type of request.
# This config file is only used by the apache2-suexec-custom package. See the
# suexec man page included in the package for more details.
Everything up to this point is a one-time, initial set-up task. The remaining configuration is done on a per site basis.
/home/user/domain/htdocs is your document root.
php-fcgi-wrapper is a shell script that contains fcgi parameters and kicks off your php5-cgi binary.
php.ini lives in a conf directory outside the document root & holds any values formerly in .htaccess
prepend.php any php code your web app needs to execute to initialise itself.
# Wrapper for PHP-fcgi
# This wrapper can be used to define settings before launching the PHP-fcgi binary.
# Define the path to php.ini. This defaults to /etc/phpX/cgi.
export PHP_INI_SCAN_DIRexport PHPRC=/home/user/domain/conf# Define the number of PHP child processes that will be launched.
# This is low to control memory usage on a server that might launch
# these processes for lots of domains.
# Leave undefined to let PHP decide.
export PHP_FCGI_CHILDREN=1# Maximum requests before a process is stopped and a new one is launched
export PHP_FCGI_MAX_REQUESTS=5000# Launch the PHP CGI binary
# This can be any other version of PHP which is compiled with FCGI support.
These are Apache directives that live in /etc/apache2/sites-available/domain.conf:
SuexecUserGroup user users
# SetHandler application/x-httpd-php
php_admin_flag engine off
AddHandler fcgid-script .php
AddHandler fcgid-script .php5
#FCGIWrapper /usr/bin/php5-cgi .php
FCGIWrapper /home/user/domain/htdocs/cgi-bin/php5-default/php-fcgi-wrapper .php
</IfModule><Directory “/home/user/domain/htdocs”>allow from all
Options +Indexes +ExecCGI
CustomLog /home/user/domain/logs/access_log “combined”
After you’ve done this, restart Apache.
Note: file ownership and groups must match SuexecUserGroup user users line above.
Permissions can be 600
Do suexec last.
Get fcgid working first, if you have problems getting it to work with .php files start with .fcgi file extension.
Testing & Debugging
Create a simple first script called first.php that contains:
<?php system('whoami'); ?>
The first line will output which user the script is running as, it should be user from our example above, if its www-data something has gone wrong. Usually you are still running mod_php.so & you’ve yet to pick up your changes – have you restarted Apache?
In the output from phpinfo() you should see:
A common error is to have first.php served as a download. This indicates mod_php.so is not being run (good!) but php-cgi cannot be found (bad!). Check
FCGIWrapper directive in Apache vhost conf file.
500 Internal server errors are typically indicate a suexec permissions problem, look in
for error messages about permissions.
If you make changes, e.g. to php.ini you must:
otherwise your tests will connect with existing fcgid processes which have your old values.
If phpinfo() does not show your correct php.ini settings there is likely a syntax error in the .ini file. Typically caused by missing = and double quotes in path information where .htaccess style is used.