Securing mod_php – without the vasectomy
In it’s most typical setup, Apache 2/mod_php is not secure enough for shared virtual hosting.
With the typical mod_php setup, a rogue webmaster or bug in a single PHP script could compromise every script and configuration file for every site hosted on the server, no matter how security conscious the developer.
So what would happen if a rogue webmaster on the same shared server discovered your database credentials..? What if they were really malicious and went further, posting your valuable data online or even selling the information to a competitor? I’ll leave that answer for you to work out.
In this article I will explain why the default Apache 2/mod_php setup is so insecure, and then go through the various methods that can be used to make it better.
The Problem in a Nutshell
In the standard Apache 2/mod_php model (with the usually default prefork mpm), all sites run within the security context of the same user (apache, nobody, www-data, depending on your distro). This model gives the best possible PHP performance in all possible use cases, hands down, but it also means that assets and code belonging to one site are fully readable from adjacent PHP applications.
Chrooted FTP won’t help you either. It might look secure from the comfort of your FTP login, but that doesn’t mean your PHP code has the same view of the system during execution. In fact I’m quite sure that for the majority of sites using shared hosting based on mod_php, it doesn’t.
Security through Obscurity
There are various ’soft’ methods used by many shared hosts in an attempt to lock down PHP; but in my opinion, none of these can really be considered “secure”. Security must be enforced from outside the PHP context otherwise you’re just playing whack-a-mole.
PHP’s safe_mode and open_basedir are the devils I speak of, but herein lies the PHP vasectomy. Just look at that list of things you can’t do with safe_mode enabled. Besides, safe_mode is deprecated now, and will go away altogether in PHP 6.0.
Open_basedir, another “soft solution” provides some additional protections but again it’s whack-a-mole, not security.
Some hosters attempt to secure their servers by implementing an obscure filesystem structure that attempts to obfuscate the path. But back in the real world any person with ability to upload/execute php code can brute force past this in no time at all.
The bottom line is, security through obscurity should never be an option – especially since options are available.
Securing PHP the expensive way…
First there was the dedicated server, then the virtual private server. Then came the virtual machine, aka ’slice’ or ‘instance’.
All of these do an excellent job of providing a partitioned security model that protects one application from another. Simply host one site per instance and each is secured from each other.
Bear in mind however that for the vast majority of small/medium sites these solutions are also overkill, inefficient with resources and downright expensive.
Securing PHP the shared way…
Securing PHP in a shared hosting environment is certainly possible, but it can only be done by the web server operator.
The basic model in securing the server is to force PHP (or any application in fact) to execute within the context of a dedicated user account which has the minimum privilege required to read files belonging to it’s designated site, but not enough privileges to see or otherwise interact with other files on the system.
Extra points can be awarded for creative filesystem permissions that keep adjacent usernames invisible too, though this is more cosmetic than anything and not really about security.
The methods available break down into two main classes - External execution and Internal execution. Each method makes it possible to provide a truly locked down PHP execution environment for each site on the server; but as you might imagine – each one comes with a cost. You didn’t think security would be free, did you?
External execution options
External execution is the method used by most (non-Apache) web servers to provide support for PHP. Apache supports these methods as well, with each providing a valid framework for locking down PHP.
- SUEXEC – Performance is extremely poor, beyond abysmal in fact. SUEXEC combines CGI execution with a change of user, creating a fresh PHP process for every single request made to the web server. Expect high load averages, slow request rates and significantly reduced server capacity as a result. Also expect less that 100% mod_php compatibility, since you are not really using mod_php, but php-cgi instead.
- FastCGI enables external execution like SUEXEC, but with process reuse. This negates most of the performance hit of SUEXEC but again uses php-cgi so is not 100% mod_php compatible. FastCGI can also be quite resource-wasteful in the special case of mass vhosting of many small web sites. This is due to each site having persistent processes, instead of sharing resources in a pool. With FastCGI, the number of worker processes must also be managed carefully to meet changes in load demand. In short expect lots of work, and lots of pain, for reasonable gain!
- SuPHP is somewhat like SUEXEC but using a modified mod_php Apache module which is probably 100% compatible with mod_php. This offers a middle ground between SUEXEC and FastCGI, and is a reasonably popular choice amongst shared PHP hosting providers.
Internal execution options
These options are very specific to Apache 2, as they require swapping the multi processing module (MPM) inside Apache itself. This can be extremely flexible as it keeps the resource management and worker pooling inside Apache – where it naturally belongs.
- MPM_peruser provides the best possible performance, by creating a seperate pool of worker processes for each vhost, and enabling worker reuse within each pool. It provides 100% mod_php compatibility too. Where it falls down is with mass vhosting, running hundreds of low traffic sites on a single server is going to tie up a lot of system resources.
- MPM_itk is 100% mod_php compatible, and provides a single pool of workers able to switch user and handle requests for any vhost on the server. This makes it more efficient than MPM_peruser in a mass vhosting scenario. To improve performance over SUEXEC/SuPHP, it doesn’t execute a new process for each process, instead using system calls to change the “effective” user id before passing the request on to PHP. The major downside is that the worker pool must have root system privileges until a vhost can be selected to serve the request. I will discuss the issues raised by this in another blog article, but it’s worth saying that this is usually frowned upon with good reason.
Conclusion
With so many options available it can be quite difficult to choose between them. What I can tell you is that we chose MPM_itk and it has worked out remarkably well for almost a year now, serving millions of pages and Terabytes of outbound data transfer per month across hundreds of sites, with very reasonable resource usage and fast performance.
By offloading static content to differently configured apache servers (again, running an alternative MPM, but that’s another article too) we’ve more than recovered any performance lost in the switch to MPM_itk.
If you are running a smaller number of busier sites then it’s probably worth checking out MPM_ peruser. With per-vhost worker pools and process reuse you’ll definitely see performance gains.
If these aren’t an option then SuPHP gives 100% mod_php compatibility at the cost of performance. SUEXEC and FastCGI, in my opinion, are at the bottom of the pile when it comes to PHP.
Of course, don’t forget that none of this helps if you haven’t locked down the rest of the OS running your web server. And if securing servers isn’t your bag but making successful web sites is – then perhaps vCluster is the way forward for you.
I’d love to hear your opinions, especially if you think I’ve missed anything…

An excellent article and well explained – you might mention that mpm_itk is not thread aware, which I appreciate is not a problem for most people.
Would love to know how to get those “extra points”…
Simon
http://www.osbornebrook.co.uk
Thanks Simon, and good spot on the threads.
To get a threaded Apache you’d need to use MPM_worker, which excludes using MPM_itk since Apache can only use a single MPM. It might be possible to combine the two, but I’m not sure (doubt?) whether it would be safe for a worker thread to attempt to change effective user id without affecting other workers too. I’d need to investigate that.
And, while PHP will technically run under a threaded web server, PHP itself carries no thread safe guarantees. Although these days they claim the core is thread safe, but that certainly couldn’t be assumed for any extensions without much closer inspection.
I’ll comment on those extra points another time
Mark, this is a great article. Can you talk about PHP opcode caches with MPM_itk – specifically do they work? Are they per user or shared across the whole machine?
I had a heck of a time trying to secure Apache for a *small* shared hosting environment (nothing close to what it sounds like you are running) while still allowing APC which speeds up my PHP sites so much. I ended up doing FastCGI with APC but I admit it is very resource intensive and would not scale to thousands of small sites.
I’d be very interested in your thoughts on opcode caches in your setup. Again – great article!
I’ve blogged about this a few weeks ago, and talked about op-code caches in ITK
http://andytson.com/blog/2010/01/techniques-for-creating-a-secure-shared-web-server/
APC definitely will not work in mpm-itk, whereas xcache and eaccelerator support file-based caching (xcache using a memory-mapped file, which would be better).
The simple reason is mpm-itk’s processing model discards modified or newly allocated shared-memory after each request.
Alternatively, as mentioned in my blog article, there is mod_apparmor, which doesn’t change the apache processing model at all. I’m planning to write a more detailed blog post on it sometime soon.
Brandon, thanks for the kind comments…
Unfortunately MPM_itk completely rules out shared memory opcode caching. It is possible to do file-based opcode caching though, and in this case MPM_itk helps to keep things nice and secure – writing cached files under the ownership of the system user associated with the vhost.
File-based caching also has a useful side-effect in a clustered web server setup, as it can save the server having to fetch the PHP source across the network assuming you cache to a local disk (most web clusters use NFS or similar to distribute files). This can make quite a difference to request throughput under high load.
Another thing to note is that we currently use PHP 5.3.1 on our vCluster, and opcode cache support for this has been quite patchy. Xcache 1.3.x was the first to support it, and eaccelerator have support in their latest release candidate.
I tried out eaccelerator-0.9.6-rc1 a few weeks ago but ran into stability issues. I see they have -rc2 now so I’ll go back and see if things have improved. I have not tried xcache yet. I’ll definitely block out some time to put latest xcache/eaccelerator to the test soon, and will post the results here.
Andy,
mod_apparmor looks really good, and would indeed make it much safer to run vanilla mpm_prefork/mod_php in a mass vhosting scenario without the performance penalty of mpm_ITK.
The only downside I could see is the administrative overhead of defining ‘hats’ for each vhost and distributing these across a cluster. Definitely seems like something to investigate further though perhaps it could be useful in addition to MPM_itk…