The challenge with securing a shared hosting server is how to secure the website from attack both from the outside and from the inside. PHP has built-in features to help, but ultimately it’s the wrong place to address the problem.

So what can Apache do to help?

It turns out that there are quite a few alternative ways that Apache can help. This article will look at what we can do with stock Apache, and the next few articles will look at what we can do with some interesting third-party Apache modules.

  • suexec: Running CGI Programs As A Specified User
  • Configuring Apache With PHP/CGI
  • Configuring suexec With PHP/CGI
  • Configuring suexec For Shared Servers
  • Some Benchmarks
  • Other Considerations
  • Conclusions

suexec: Running CGI Programs As A Specified User

To secure a shared hosting server, we want to be able to run PHP as the user who owns that particular website. One way to do this with stock Apache is with suexec.

suexec is a standard Apache module which allows you to run a CGI executable as a specified user and group. CGI executables date back to the very early days of the web, back when we all had to use Perl to create dynamic websites. Although PHP is commonly run as an Apache module, it still provides support for CGI.

Check with your Linux vendor to make sure that you have PHP/CGI installed on your box.

Configuring Apache With PHP/CGI

The first step for getting suexec working is to configure Apache to run PHP as a CGI executable, instead of using mod_php. Add the following configuration to your httpd.conf file:

ScriptAlias /php5-cgi /usr/bin/php-cgi
Action php5-cgi /php5-cgi
AddHandler php5-cgi .php
AddDirectoryIndex index.php

… and add the following line to your virtual host:

AddHandler php5-cgi .php

In your httpd.conf file (or in one of the files that httpd.conf includes), there will be a <Directory> entry for the directory on disk where your virtual host is stored. Inside that <Directory> entry, there should be an “Options” line, which might look like this:

Options Indexes FollowSymLinks

Add “ExecCGI” to the end of your Options line.

Make sure to comment out mod_php from Apache. Then, restart Apache, and do some testing to make sure that PHP 5 is working.

For reference, here is the Apache config from my test system:

ScriptAlias /php5-cgi /usr/bin/php-cgi
Action php5-cgi /php5-cgi
AddHandler php5-cgi .php
AddDirectoryIndex index.php index.phtml

<VirtualHost *:80>
        DocumentRoot /var/www/localhost/htdocs
        <Directory /var/www/localhost/htdocs>
                Options Indexes FollowSymLinks ExecCGI
                AllowOverride All
                Order allow,deny
                Allow from all
       </Directory>
        AddHandler php5-cgi .php
</VirtualHost>

Configuring suexec For PHP/CGI

With Apache now running PHP as a CGI executable, we’re ready to get Apache running PHP as the owner of each website.

In your test virtual host, add the following:

SuexecUserGroup stuart users

Replace “stuart” with the user who owns the website, and replace “users” with the group that the user belongs to. This sets the privileges that PHP will run as.

To ensure the security of your server, suexec is very particular about what conditions must be met before it will execute your PHP scripting engine. A full list of conditions can be found in the Apache docs. To make sense of the conditions, you’ll need to know what settings your copy of suexec has been compiled with. Run the command suexec -V to find out your system’s settings. This is the output from my Seed Linux LAMP Server system:

belal vhosts.d # suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_EXEC="/var/log/apache2/suexec_log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_SUEXEC_UMASK=077
 -D AP_UID_MIN=1000
 -D AP_USERDIR_SUFFIX="public_html"

The first condition (and one that isn’t obvious from the Apache manual!) is that the PHP CGI executable must be installed under AP_DOC_ROOT. Chances are that it isn’t installed there at the moment, so go ahead and copy it there.

mkdir /var/www/localhost/cgi-bin
cp /usr/bin/php-cgi /var/www/localhost/cgi-bin

The second condition is that the PHP CGI executable must be owned by the same user and group you listed in the SuexecUserGroup statement earlier. This causes problems for shared hosting; I’ll show you how to fix that later in this article.

chown stuart users /var/www/localhost/cgi-bin/php-cgi

Update your Apache httpd.conf file to use this copy of PHP:

ScriptAlias /php5-cgi /var/www/localhost/cgi-bin/php-cgi

Restart Apache, and test to make sure that PHP 5 is still working. You should also start to see log messages appearing in AP_LOG_EXEC. This is the first place to look if PHP isn’t working (although the log messages can be a little terse and cryptic).

For reference, here is the Apache config from my test system:

ScriptAlias /php5-cgi /var/www/localhost/cgi-bin/php-cgi
Action php5-cgi /php5-cgi
AddHandler php5-cgi .php
AddDirectoryIndex index.php index.phtml

<VirtualHost *:80>
        DocumentRoot /var/www/localhost/htdocs
        <Directory /var/www/localhost/htdocs>
                Options Indexes FollowSymLinks ExecCGI
                AllowOverride All
                Order allow,deny
                Allow from all
        </Directory>
        SuexecUserGroup stuart users
        AddHandler php5-cgi .ph
</VirtualHost>

Configuring suexec For Shared Servers

I mentioned earlier that there was a problem with using suexec + PHP/CGI on shared servers – the very environment where suexec is needed the most :( In one of the steps above, we created a copy of the PHP CGI executable, and changed its ownership on disk to match the ownership of the website.

chown stuart users /var/www/localhost/cgi-bin/php-cgi

What happens when we have two websites, each owned by a different user? Or five, or ten, or hundreds? Apache’s suexec will refuse to re-use this copy of the PHP CGI executable for each of the websites, because it isn’t owned by the right user and group.

Each website needs its own copy of the PHP CGI executable, owned by the user and group that owns the website itself. We don’t want to create hundreds of copies of the actual PHP CGI executable (it’s a large waste of space, and a pain for managing PHP upgrades), so instead we can point each website at its own copy of a simple bash script:

#!/bin/bash

/usr/bin/php-cgi "$@"

This script simply executes our central copy of the PHP CGI executable, passing through whatever parameters Apache has called the bash script with.

To configure Apache to use this script, simply move the ScriptAlias statement from outside the VirtualHost config to inside.

Some Benchmarks

Because Apache is having to execute a new suexec process every page hit (and suexec executes a new PHP CGI process every page hit), it’s going to be slower than running mod_php. But how much slower? To find out, I used Apache’s ab benchmarking program to load a phpinfo() page 1000 times. I ran the benchmark five times and averaged out the results.

  • suexec: average of 127.219 seconds
  • suexec + bash script: average of 134.836 seconds
  • mod_php: average of 3.753 seconds

suexec on its own is some 34 times slower than using mod_php; suexec + the bash script needed for shared hosting environments is even worse, at 36 times slower than using mod_php.

This benchmark doesn’t provide the full picture. Once you take into account the extra memory used by the suexec method, and the extra memory and CPU (and process context switches!) required to transfer output from PHP/CGI to Apache to send back to the website’s user, the final cost of using suexec + PHP/CGI will be substantially higher.

Other Considerations

Performance isn’t the only thing to think about when evaluating suexec + PHP/CGI.

  • suexec + PHP/CGI does solve the security challenge, without requiring your application to support safe_mode.
  • HTTP authentication is only supported by mod_php, not PHP/CGI. If your application relies on this, then suexec + PHP/CGI is not for you.

Conclusions

Apache’s suexec mechanism does secure a shared hosting server from attack from within. However, this is achieved at a heavy performance cost, which inevitably will translate into needing lots of extra servers – which is expensive.

So, if Apache itself doesn’t come with a solution that’s worth a damn, maybe there are third-party solutions out there that can do a better job? The next article in the series will take a look at what others have done to try and plug this gap.

This article is part of The Web Platform, an on-going series of blog posts about the environment that you need to create and nurture to run your web-based application in. If you have any topics that you d like to see covered in future articles, please leave them in the comments on this page.

19 Comments

  1. sapphirecat says:
    December 18th, 2007 at 1:24 pm

    Is it more efficient to use ‘exec’ so the shell doesn’t fork+wait on php-cgi? I’m curious what the benchmark result is if you change the bash script like so:

    exec /usr/bin/php-cgi “$@”

    It also seems fairly trivial to write a small C program to do this; you’d just pass your own argv on to the appropriate exec function. You might even statically link it, to avoid running ld.so. But I suspect this is leading into the land of tedious micro-optimization; especially, the speed of “execute PHP directly” is the limit for this case.

  2. developercast.com » Stuart Herbert’s Blog: Using suexec To Secure A Shared Server says:
    December 18th, 2007 at 6:52 pm

    [...] His guide steps through the entire process – getting the software, configuring Apache (with the PHP/CGI installation) and configuring suexec, both for the default install and then for the shared server settings. There’s even a few brief benchmarks showing the speed of execution for scripts with and without the suexec environment.   [...]

  3. David Rodger says:
    December 18th, 2007 at 11:43 pm

    It would be interesting to read your thoughts about this (if you’ve not come across it previously)…

    http://www.telana.com/peruser.php

  4. Stu says:
    December 20th, 2007 at 8:25 am

    @sapphirecat: Using exec or a trivial C program would both be more optimal than using the script I supplied, definitely.

    @david: Thanks. I’ll cover that in the next article or so!

  5. Sasa Ebach says:
    December 20th, 2007 at 12:22 pm

    Stu, this is much appreciated. I can’t wait for your next article or a good solution for this problem. The best thing I could come up with would be one Apache for each user and a master Apache which works as proxy to the user instances.

    I figure this is probably very inefficient if you have dozens or hundreds of accounts. I have not actually tried this, yet.

  6. Adam says:
    January 9th, 2008 at 1:50 pm

    Good article! Thanks a lot for sharing.

  7. Phill Brown says:
    July 9th, 2008 at 3:48 pm

    Great Article,
    After reading I am now swayed more to utilise suPHP or FastCGI Instead.
    Thanks.

  8. Fwolf’s Blog » Blog Archive ??????????php?????? - Fwolf's Blog says:
    July 13th, 2008 at 10:47 am

    [...] suphp?suexec?????dv3.0?php5??????????suphp?????suphp_mod_php???????mpm-peruser?????????????????? [...]

  9. tanuj says:
    July 16th, 2008 at 11:09 pm

    how can i check myserver having suexec enabled.

    Regards,
    Tanuj
    http://www.isuvidha.com/tanuj

  10. Stuart Herbert On PHP - » Can You Secure A Shared Server With PHP + FastCGI? says:
    October 7th, 2008 at 8:29 am

    [...] at is using PHP + FastCGI. The belief is that using FastCGI will overcome the performance issues of Apache’s suexec or mod_suphp, because FastCGI processes persist between page [...]

  11. posicionamiento says:
    October 19th, 2008 at 12:15 pm

    for those who have the same question as tanuj … If you want to know what module are running on your apache web server.

    earth:/home/srv/myuser/# apache2 -t -D DUMP_MODULES
    Loaded Modules:
    core_module (static)
    log_config_module (static)
    logio_module (static)
    mpm_prefork_module (static)
    http_module (static)
    so_module (static)
    actions_module (shared)
    alias_module (shared)
    auth_basic_module (shared)
    authn_file_module (shared)
    authz_default_module (shared)
    authz_groupfile_module (shared)
    authz_host_module (shared)
    authz_user_module (shared)
    autoindex_module (shared)
    cgi_module (shared)
    dir_module (shared)
    env_module (shared)
    mime_module (shared)
    negotiation_module (shared)
    perl_module (shared)
    php5_module (shared)
    rewrite_module (shared)
    setenvif_module (shared)
    status_module (shared)
    suexec_module (shared)
    userdir_module (shared)
    ssl_module (shared)

    This is on debian. Check if you apache executable is apache apache2 httpd or httpd2 on your distro or operating system

  12. Alexandre THIL says:
    April 10th, 2010 at 6:29 pm

    Thanks for your tutorial, it works …

    Except for your bash script “#!/bin/bash …… /usr/bin/php-cgi “$@”
    It don’t work, i don’t know why.

    Here’s the begin of my vhost:

    SuexecUserGroup “#1000″ “#1000″
    ScriptAlias /php5-cgi /home/server/cgi-bin/php5.fcgi
    Action php5-cgi /php5-cgi
    AddHandler php5-cgi .php

  13. Gentoo SysCP Installation – fcgid and suexec « maugustyniak says:
    July 3rd, 2010 at 2:33 am

    [...] http://blog.stuartherbert.com/php/2007/12/18/using-suexec-to-secure-a-shared-server/ [...]

  14. Alwina says:
    July 19th, 2010 at 11:10 pm

    Hi Stuart,

    Your benchmarks made me decide to try to use to instances of apache for a secured hosting platform on freebsd. One instance will use SSL and suexec with a performance penalty. The other will just run as Apache and have a RO filesystem… Thanks a lot for your great research on this subject!

    Regards,
    Alwina

  15. Apache error with suEXEC only Drija says:
    November 19th, 2010 at 10:11 am

    [...] I enable suEXEC by following the tutorial here, I am able to get PHP to run over Apache in cgi mode, but when I start trying to use suEXEC I get a [...]

  16. Apache error with suEXEC only says:
    November 20th, 2010 at 6:34 am

    [...] I enable suEXEC by following the tutorial here, I am able to get PHP to run over Apache in cgi mode, but when I start trying to use suEXEC I get a [...]

  17. Do I need to worry about suexec and suphp on my VPS? says:
    November 25th, 2010 at 7:26 am

    [...] can’t find a more recent article, but this 2007 post benchmarking suexec says it’s about 30x slower than regular [...]

  18. Apache error with suEXEC only - Admins Goodies says:
    August 18th, 2011 at 2:57 pm

    [...] I enable suEXEC by following the tutorial here, I am able to get PHP to run over Apache in cgi mode, but when I start trying to use suEXEC I get a [...]

  19. LAMP Server says:
    August 19th, 2011 at 3:32 pm

    I agree with that..