Tune Apache (httpd) mpm_worker on cPanel WHM

Decorative title image of server in rack with cPanel logo

All product names, logos, and brands used in this post are property of their respective owners.

Over the last several years, I’ve worked a lot with cPanel (phenomenal product if you are in the market for a web hosting control panel - easy for sysadmins and developers). I’ve run a variety of web workloads on the platform and it never ceases to amaze me (plus, you cannot beat the EDU/non-profit licensing).

At one point, I was tasked with tuning apache on a cPanel server with a slightly different workload (1 very heavily trafficked application) and was provided with a high-performance virtual machine (example setup: 4 CPUs, 12 GB memory) to accommodate. The application was to use SSL (HTTPS), so I opted for mpm_worker (mpm_event is nice, but performs similarly to worker when SSL is in use).

Much literature exists regarding tuning mpm_worker for small (low memory, low end) virtual private servers (VPS), but I was not able to find much information about tuning apache for a larger (high end, high memory) server. For the remainder of this post, I will assume that you have already configured httpd to use the worker mpm (with EasyApache) on your cPanel server. Also note, this process is generalized and will apply best to well-balanced VPS (CPU count scaled to match the amount of memory - say 1 CPU core for every 2-3 GB of RAM).

Quick tips

  • cPanel/WHM does not allow adjustment of ThreadsPerChild via the web interface - it defaults to 25 (you can change this in httpd.conf and distill the configuration BUT I advise against that)
  • Given the above, MaxClients must be divisible by 25 (ThreadsPerChild)
  • Most tuning is achieved by changing MaxClients (MaxRequestWorkers) and ServerLimit

Prework (initial calculations)

To tune httpd and mpm_worker, a little background information is required; the information gathering process will vary a little from server to server.

First, you must calculate the average amount of memory (RAM) that each apache process uses. When performing this calculation, the server should be under “normal” load. If it is idle, the calculation will be inaccurate.

I use top -u and specify the username that httpd runs as (on cPanel, this is “nobody” but the user varies on other distributions depending on your configuration). We are most interested in the RES column - more specifically, the average RES for all apache processes.

As an example:

# top -u nobody

Tasks: 255 total,   1 running, 254 sleeping,   0 stopped,   0 zombie
Cpu(s):  4.0%us,  0.3%sy,  0.0%ni, 95.6%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  12303646k total,  6088432k used,  9259430k free,   520936k buffers
Swap:  2097148k total,        0k used,  2097148k free,  3804344k cached

27550 nobody    20   0 2126m 113m  74m S  7.0  0.5   0:56.28 httpd
27366 nobody    20   0 2125m 115m  74m S  5.6  0.5   1:00.96 httpd
29001 nobody    20   0 2126m  45m 6788 S  5.3  0.2   0:25.52 httpd
28127 nobody    20   0 2126m  48m 6228 S  5.0  0.2   0:48.01 httpd
28048 nobody    20   0 2126m 114m  74m S  4.7  0.5   0:48.76 httpd
28827 nobody    20   0 2125m  44m 6072 S  2.7  0.2   0:30.49 httpd
30106 nobody    20   0 2112m  31m 3668 S  2.7  0.1   0:03.35 httpd
 2922 nobody    20   0  108m  14m  796 S  0.0  0.1   1:47.00 httpd

Then, calculate the average memory (RES) consumed per process by adding each RES value and dividing by the number of processes:

113 + 115 + 45 + 48 + 114 + 44 + 31 + 14 = 524 / 8 = 65.5

This means each apache/httpd process consumes ~66 MB of memory on average. cPanel uses the default value of 25 for ThreadsPerChild, so we can conclude that for every 25 clients, ~66 MB of memory is required.

Calculating your server’s value for MaxClients (aka MaxRequestWorkers)

Next, you must decide how much memory you can allocate for httpd and impose that limit with Max Clients. To get a baseline, I recommend stopping apache and assessing the amount of available memory on the system.

On the example server, let’s say that 9259430k (~9.5 GB) of memory is available when httpd is not running. Say you are comfortable allocating 75% of that (~7 GB) to Apache. Use your judgment here - depending on your system, you may need to adjust your allocation (to account for things like cPanel/WHM, PHP, MySQL, and other processes). Several factors can affect memory usage as load increases so it is not safe to assume that what is available when apache is stopped will scale linearly. Under no circumstances should you allow apache to swap. If that occurs, your estimate/allocation is too high.

Let’s calculate!

Above, we decided that we can spare 7 GB of memory for httpd. We also know that our average apache memory usage per process is 66 MB.

7 GB * 1024 = 7168 MB / 66 MB (from above) = 108.6 processes
Then round down = 100 processes

This means ~100 apache processes can run in the 7 GB we allocated. We know that each apache process can handle 25 clients (threads), so multiply by ThreadsPerChild to obtain Max Clients:

100 * 25 = 2500

So for this server, a Max Clients setting of 2500 is reasonable (and since Server Limit simply caps the upper limit, set it to 2500 or greater). Since we rounded down to the nearest 100, MaxClients will be divisible by 25 (and it needs to be). If your calculation is different, round down appropriately so that your Max Clients setting is divisible by 25.

Updating cPanel’s httpd configuration

Implementing these settings in WHM is easy:

WHM -> Service Configuration -> Apache Configuration -> Global Configuration

Then, update the 2 settings based on your calculation:

WHM Apache Global Configuration Options

Finally, restart apache, and you should be good to go.

It could not hurt to perform a load test against your server as a sanity check (JMeter is free and well documented). This will ensure your estimate/calculation from above is within spec. Again, keep httpd from swapping at all costs. A load test will also help identify other potential bottlenecks on your server (CPU, disk performance, network bandwidth, etc.). At this point, you may also consider putting your extra threads to work (or wait) by enabling KeepAlive and increasing your Keep Alive Timeout (if you have not already).