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.

I’ve already written about a number of solutions that work, but one option I’ve been asked time and time again to look 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 views.

But before we can look at performance, the first question is: how exactly do we get PHP and FastCGI running as different users on the one web server in the first place?

Installing And Configuring mod_fcgid for Apache

Stock Apache does not ship with built-in support for FastCGI. You need to download and install a third-party module. There are two choices – the original mod_fastcgi, and the more recent mod_fcgid, which I’ll look at in this article. Before we start, make sure that you have built and configured Apache to use suexec. We will reuse suexec to ensure that our FastCGI PHP processes run as different users.

Most Linux distributions already include a package for mod_fcgid; you should be able to install it using your distro’s package manage. The version I’ve tested for this article is mod_fcgid 2.2 running on Seed Linux (a Gentoo-based distro).

After installing mod_fcgid, make sure that you edit your Apache config files, and comment out any lines that load mod_php. They will look like this:

LoadModule php5_module modules/libphp5.so

Then, add the following lines to your virtual host.

SuexecUserGroup myuser mygroup

<Directory /var/www/localhost/htdocs>
AddHandler fcgid-script .php
Options ExecCGI
Allow from all
FCGIWrapper /var/www/localhost/cgi-bin/php.fcgi .php
</Directory>

Replace “myuser” with the user who owns the website, and replace “mygroup” with the group that the user belongs to. This sets the privileges that PHP will run as when this website is visited.

Because suexec is understandably paranoid about what CGI programs it will run for you, to make mod_fcgid work in a shared hosting environment, we need to create a FastCGI wrapper script owned by the same user that owns the website:

#!/bin/bash
PHPRC=/etc/php/apache2-php5
export PHPRC
PHP_FCGI_CHILDREN=4
export PHP_FCGI_CHILDREN
PHP_FGCI_MAX_REQUESTS=5000
export PHP_FCGI_MAX_REQUESTS
exec /usr/lib/php5/bin/php-cgi

Each website needs its own copy of the script. Place this script in the website’s dedicated cgi-bin directory. This should be a directory that you control to make sure that malicious scripts cannot be uploaded to take advantage of suexec. Make sure that the script is owned by the user and group who owns the website, otherwise suexec will refuse to run the script, and you’ll spend quite a bit of time scratching your head wondering what the problem is!

The FastCGI wrapper script gives us an opportunity to set limits on how PHP works as a FastCGI process. We can tell it how many FastCGI scripts are allowed to run (to make sure one website doesn’t use up all of the web server’s free capacity), and also how many HTTP requests each FastCGI process should handle before terminating (to limit the impact of memory leaks).

At this point, you can restart Apache, and you should find that your websites are now using suexec + FastCGI to run as separate users.

Making Apache Go Even Faster

One of the major benefits of using Apache 2.2 over Apache 1.3 is the ability to switch how Apache works at the fundamental level. Apache MPMs (multi-processing modules) can emulate Apache 1.3′s behaviour (mpm-prefork) … but it can also provide new options. By default, most (if not all) Linux distributions install Apache 2.2 built with mpm-prefork, but by switching to another MPM, can we make our websites go even faster?

If you are using suexec + mod_fcgid on Linux, there are two MPMs available to you that have the potential to boost performance further: mpm-worker and mpm-event. Both MPMs turn Apache into a multi-threaded server. On Linux systems, it is usually much quicker to create new threads than it is to create new processes. The downside is that software has to be specially written to work correctly in a multi-threaded application (known as being thread-safe). mod_php doesn’t work reliably with mpm-worker and mpm-event, because it reuses a lot of third-party code that may or may not be thread-safe. But because we’re running PHP in a separate FastCGI process, we can safely turn Apache into a multi-threaded server.

Some Benchmarks

To benchmark PHP + FastCGI + suexec, I used Apache s ab benchmark to load a simple phpinfo() page 1,000 times. I ran the benchmark five times, and averaged the results. To compare the results, I repeated the tests against mpm-worker, mpm-event, and mpm-prefork both with and without mod_php.

  • mpm-worker + mod_fcgid + PHP/FastCGI + suexec: 7.36 seconds, 0.2% failure rate
  • mpm-event + mod_fcgid + PHP/FastCGI + suexec: 7.75 seconds, 0.2 % failure rate
  • mpm-prefork + mod_fcgid + PHP/FastCGI + suexec: 7.92 seconds, 0.2% failure rate
  • mpm-prefork + mod_fastcgi + PHP/FastCGI + suexec: 8.52 seconds, 0.2% failure rate
  • mpm-prefork + mod_php: 7.38 seconds, 0% failure rate

Other Considerations

The performance is good, especially if you switch Apache MPMs. These benchmarks are extremely simplistic, and what they don’t show is that switching to mpm-worker and mpm-event will probably speed up your websites even further, because these Apache MPMs handle downloading images more efficiently than mpm-prefork can. You may also be able to scale your websites better before having to upgrade your servers or add additional ones, especially if you use a bytecode cache such as APC or xcache.

But what are the downsides?

  • As with straight suexec, you can’t use HTTP authentication in your application. Hardly any apps rely on this functionality any more (probably because so many shared hosting servers use suexec).
  • Your server may require extra RAM to cope with the number of FastCGI processes running simultaneously. You may need to switch to a 32-bit Linux kernel that supports PAE or to a 64-bit Linux distro.
  • Apache + PHP/FastCGI is not 100% reliable in my testing.

Conclusions

It is possible to combine PHP, FastCGI and suexec to produce a solution that secures a shared hosting server and at the same time provides good performance compared to the alternatives. If you’re prepared to compile Apache from source and switch MPMs, you can squeeze even more performance from this combination, and perhaps even out-perform the venerable mod_php.

Unfortunately, my experience was that the PHP + FastCGI combination cannot be trusted to serve pages 100% of the time. The average failure rate was 2 requests per 1000, and the failure rate was consistent no matter which Apache MPM was used, which Apache FastCGI module was used, and how many thousands of requests I used in my testing. At the time of writing, I haven’t tracked down the cause of this failure, and it may not appear in your own environment, but none of the previous solutions I’ve looked at in this series have displayed this problem, so it’s something to think about before chosing PHP + FastCGI to serve your websites. I’m hoping to find time in the future to get to the bottom of this problem, if no-one gets there first.

As a result, I can’t recommend using PHP/FastCGI + suexec at this time. My current recommendation is mpm-itk, which has successfully served millions of page hits for me in production over the last few months.

References

This article was made possible by information already on the internet:

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.

14 Comments

  1. Chris Kelly says:
    October 7th, 2008 at 10:30 am

    I haven’t experienced the 2/1000 failure rate that you have! All of my problems have been a result of the previous version of APC which would cause pages to show up blank until httpd was gracefully restarted, but the most recent version of APC hasn’t caused these problems. hmm.

  2. Martin Fjordvald says:
    October 7th, 2008 at 11:22 am

    Have you considered perhaps using a different web server such as lighttpd? I use lighttpd 1.5.0 without any problems. The downside would be no mod_rewrite through .htaccess though, so I’m not sure how well it would work for your average shared hosting server.

  3. Lafriks says:
    October 7th, 2008 at 6:01 pm

    I’m also using lighttpd and php-fcgi for shared hosting security and had no problems with such configuration. I have to help out clients with porting apache mod_rewrite syntax to lighttpd but it’s usually only once for new client.

  4. Enlaces interesantes says:
    October 7th, 2008 at 7:32 pm

    [...] Can You Secure A Shared Server With PHP + FastCGI? [...]

  5. Radical says:
    October 8th, 2008 at 7:18 am

    Great article.
    I was working on a similar setup but for LigHTTPD.

    Nice to know this is working in Apache also.

  6. ah83 says:
    October 8th, 2008 at 9:35 am

    we use php as cgi with suexec and a php-wrapper written in c.
    the performance is really good on a dell server with 2 quadcore opterons and 8 GB RAM.

    even typo3 runs great on this platform.

    i like the apache suexec cgi setup, because it is simple and well understood and i’m not relying on some esoteric thirdparty apache module.

    this setup is ideal for masshosting, but on a server with only 30 sites and a lot of requests i would run apache instances for every site.

    litespeed is also a good option, because it works like the fastcgi solution but more
    stable.

  7. Stuart Herbert’s Blog: Can You Secure A Shared Server With PHP + FastCGI? : Dragonfly Networks says:
    October 8th, 2008 at 10:32 am

    [...] a new post today Stuart Herbert asks the question “is it possible to secure a shared server with PHP and [...]

  8. Michael VanDeMar says:
    October 9th, 2008 at 5:58 pm

    I have been trying for a week now to get this running on my server, with some frustrating results. Now I’m getting this in suexec.log:

    [2008-10-09 09:13:33]: uid: (500/kitten-art) gid: (501/501) cmd: kitten-art-wrapper

    where kitten-art is the user and kitten-art-wrapper is the FCGIWrapper, but then getting a 500 error with this in the httpd error log:

    [Thu Oct 09 09:13:33 2008] [notice] mod_fcgid: call /var/www/html/domain.com/public_html/index.php with wrapper /var/www/fastcgi-kitten-art/kitten-art-wrapper
    suexec failure: could not open log file
    fopen: Permission denied
    [Thu Oct 09 09:13:36 2008] [notice] mod_fcgid: process /var/www/html/domain.com/public_html/index.php(17346) exit(communication error), terminated by calling exit(), return code: 1

    Since all of the logs are in fact getting written to as far as I can tell (both Apache and site specific ones), I’m not sure what the error is referring to. Any ideas?

  9. Travers Carter says:
    October 12th, 2008 at 12:59 am

    I run a similar setup to the one you describe here on several of our servers both for security, and the ability to run parallel PHP versions (the main difference in my config is I use a custom suexec replacement that removes the need to have a wrapper script per vhost) and initially had a similar problem with request failures.

    I believe your 1 in 500 failure rate comes from two things:
    1) In your wrapper script there is a typo “PHP_FGCI_MAX_REQUESTS=5000″ should be “PHP_FCGI_MAX_REQUESTS=5000″, if this typo is in the actual script PHP would be defaulting to 500 requests maximum per process

    2) With php you need to use the mod_fcgid “MaxRequestsPerProcess” option to avoid a race condition in php’s shutdown when it reaches the last request, see the last part of http://fastcgi.coremail.cn/doc.htm (it should be tuned to match your PHP_FCGI_MAX_REQUESTS)

    Also as far as I understand mod_fcgid will never send a request to a worker until is has finished with the previous one, so setting PHP_FCGI_CHILDREN=4 will just add unnecessary startup overhead, you are better off letting mod_fcgid start multiple php processes itself.

  10. Nginx support says:
    December 10th, 2008 at 8:11 am

    good benchmarsk, but why was failures in tests ?
    This schemes very good for shared hosting, cause useful and secure.

  11. Marco says:
    June 30th, 2009 at 8:41 pm

    there is a workaround to make php http auth to work using mod_rewrite

    Using .htaccess

    RewriteEngine on
    RewriteRule .* – [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

    Then in php script before user/pass:
    list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(‘:’, base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));

  12. Ingo says:
    November 12th, 2009 at 4:27 pm

    I want to confirm what Travers Carter said about the failure rate.

    I also had tested fastcgi for various reasons, especially since it seems to be the fastest alternative and i was encountering these failure rates, like 1 to 3 failed pages in 1000 requests (with a concurrency level of 100).

    I just made a series of Tests with having PHP_FCGI_MAX_REQUESTS set (some with 5000 some with 2000) and exportet in the Wrapper and also without exporting it. In all my Tests having it set, i had no failures, while when not set i had the mentioned 1 to 3.

    Thanx Travers for the tip and off course to Stuart for the good write up.

  13. dennyhalim.com says:
    March 9th, 2010 at 11:56 am

    if i understand this correctly:
    http://wiki.apache.org/httpd/PrivilegeSeparation

    suexec+fcgid is the only recommended method (by apache people or the wiki author)

  14. Jeremy says:
    May 25th, 2010 at 10:28 pm

    What is the situation right now? What solution is recomended for the production servers? I already did the itk configuration, but fcgid seems to be faster and maybe better (peruser works fine for my home server, but the authors claim this is not production ready MPM), but I don’t know how about serving non php/cgi scripts and full Privilege Separation. CGI scripts are setuid’ed to particular user, but how .html pages can be served?