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. Apache has built-in features too, but the performance cost of these features is prohibitive.
This has created a gap that a number of third-party solutions have attempted to fill. One solution you may have heard of is mpm-peruser, by Telana Internet Services. How well does it work, and how well does it perform?
- A Bit of History
- Installing mpm-peruser
- Configuring Apache
- Some Benchmarks
- Other Considerations
A Bit Of History
One of the major differences between Apache 1.x and Apache 2.x is Apache 2.x’s ability to change the core processing engine at compile-time. These core processing engines are known as multi-processing modules (MPMs), and originally they were used to provide better support for different legacy platforms such as Netware, OS/2, and Windows NT. But that wasn’t all that MPMs were good for, and Apache 2.0 also included several experimental MPMs, which were later abandoned for one reason or another.
One of those experimental MPMs was mpm-perchild, a multi-threaded attempt at providing a secure solution for shared servers. This was never going to be a good solution with PHP (which doesn’t work reliably in a threaded environment), and by 2004 the module had been abandoned (and perhaps it never worked in the first place). However, the idea that it’s Apache that isn’t secure enough for a shared server stuck, and mpm-perchild was the inspiration behind the metuxmpm, created by Enrico Weigelt.
By all accounts, metuxmpm worked where mpm-perchild didn’t. It created one Apache process per user, and then each of these Apache processes creates separate threads to handle requests. Sounds like a great solution for hosting Rails on a shared box … but again, a threaded Apache is no home for mod_php
Enter the mpm-peruser module, created by Sean Gabriel Heacock at Telana Internet Services. mpm-peruser is based on metuxmpm, but it has stripped out all of the threading support, and instead creates pools of Apache processes to handle the incoming requests. Each pool is dedicated to a single user. At first glance, it sounds perfect for running mod_php on a shared server, but how well does it work in practice?
mpm-peruser is available as a patch against the Apache source code, and is bundled with Gentoo. The mpm-peruser website comes with excellent instructions on how to patch and build Apache.
You don’t need to comment out your configuration for mod_php. mpm-peruser is a replacement for the mpm-prefork module, and it uses mod_php to run your PHP scripts.
Make sure that your mpm-prefork directives in httpd.conf are surrounded by suitable directives. Most Linux distros already have these directives in httpd.conf.
<IfModule mpm_prefork_module> StartServers 5 MinSpareServers 5 MaxSpareServers 10 MaxClients 150 MaxRequestsPerChild 10000 </IfModule>
Here’s an example set of mpm-peruser directives to add to httpd.conf:
<IfModule mpm_peruser_module> # Number of spare processors to keep MinSpareProcessors 2 # Minimum number of processors to have at all times MinProcessors 2 # Maximum number of processors allowed in total # Tune this to suit local RAM + CPU MaxProcessors 10 # Maximum number of connections allowed MaxClients 150 # How many requests before we recycle the processor processes? MaxRequestsPerChild 1000 # Shutdown processors after X seconds of no requests IdleTimeout 120 # Forcibly terminate processors after X seconds of no response ExpireTimeout 120 # Leave KeepAlives off, as they're reported not to work reliably KeepAlive Off # Create processes to decide where incoming connections should go # Multiplexer <user> <group> # # Never *ever* run any daemon as nobody / nobody, including a multiplexer Multiplexer apache apache Multiplexer apache apache # This is a list of the user / group combinations that we want mpm-peruser # to run as # # One combination per Processor statement # Processor <user> <group> [<chroot>] Processor stuart stuart Processor alice alice </IfModule>
You’ll also need to add a ServerEnvironment directive to your virtual hosts:
<VirtualHost *:80> ServerName localhost ... <IfModule mpm_peruser_module> ServerEnvironment stuart start </IfModule> </VirtualHost
To help explain what the configuration does, this is how mpm-peruser routes incoming HTTP requests through to mod_php:
- One Apache process runs as root, listening on port 80 et al. It accepts incoming requests, and passes them on to the multiplexer processes.
- Because this process is also the only one that runs as root, it is the only process that can fork off new processor processes.
- Multiplexers are the processes that examine each incoming request to determine which processor it should go to. On busy servers, having multiple multiplexers can (in theory at least) improve performance. I will benchmark this later to see if that’s the case or not.
- Processors are where mod_php runs. Each processor runs as a separate process, and as set user & group, which is where mpm-peruser gains its improved security from. Once the request is complete, each processor hangs around until the IdleTimeout is reached; this is where mpm-peruser gains its improved performance from.
- Finally, each vhost needs to say which user and group it should run under, using the ServerEnvironment directive. This must match one of the processors defined in the main httpd.conf.
The result is that mpm-peruser delivers the execution-in-isolation security of suexec or suphp with a lower overhead. But just how low?
On previous posts, folks have rightly commented that these benchmarks are only measuring the relative time it takes to start up PHP. These figures don’t give the best idea of just how well each of these solutions scales. Once I’ve finished looking at each of the different shared server solutions, I’ll be publishing an article measuring scalability with some real-world examples.
To benchmark mpm-peruser, 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.
- mpm-peruser: average of 6.95 seconds
- mpm-prefork: average of 6.51 seconds
At first glance, mpm-peruser looks like a fantastic solution for dealing with the unique security problems of a shared server. But what are the possible downsides?
- mpm-peruser is known to work on Linux. It may or may not work on other platforms (e.g. FreeBSD, Solaris).
- The latest mpm-peruser patch is for Apache 2.2. There is a patch available against Apache 2.0 as well. Folks who continue to swear by Apache 1.3 will have to miss out.
- I haven’t tested it with SSL support. There again, SSL doesn’t mix well with shared servers anyway, and folks who do want SSL on shared servers probably don’t know enough about data security to be trusted with the data that you’re trying to protect via SSL!
- Speaking of security, I’m a little nervous about the process listening on port 80 running with full root privileges. Does this leave the ever-so-slight risk of a security hole in mpm-peruser allowing someone to gain full root access to the box?
mpm-peruser is harder to install than suexec or suphp, and it requires a fair bit of work to tune the configuration to suit your server’s available RAM and CPU power. But if you’re willing to put in the effort, what you get is a high-performance solution to the problem of how to secure a shared hosting server.
But can we get even higher performance without compromising on security? In the next article, I’ll be looking at mpm-itk, which potentially does both.
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.