← Blog

Apache as cPanel® configures it, next to corehttpd, on the same server

One server, one WordPress site, one PHP-FPM pool. We swapped Apache — configured the way cPanel sets it up — for corehttpd and measured pages, crowds and bytes.

Illustration of two paths to the same server: on one, a crowd of small light points is stopped in a long queue in front of a narrow barrier; on the other, a bright blue channel carries the same points in a steady line straight into the server

In short: on a 4-vCPU server we ran the same WordPress site behind Apache, set up exactly the way cPanel® sets it up, and behind corehttpd, the web server CorePanel ships. A WordPress page took 535 ms behind Apache and 191 ms behind corehttpd, and the server delivered 4.6 pages per second instead of 14.6. With 1,000 visitors browsing, a newly arriving visitor waited 18.7 seconds for Apache and 3.7 milliseconds for corehttpd. When we turned Apache’s PHP-FPM, HTTP/2 and compression on in WHM, the WordPress numbers matched — and the crowd and compression numbers did not.

corehttpd is the web server that sits at the front of every CorePanel server. We have written about why we built it; this post is what happens when you put it next to the web server most hosting servers still run, on the same machine, and measure.

The comparison that matters to a hosting provider is not “Apache in the abstract” — it is Apache as it is actually deployed. On most shared hosting servers, that means Apache configured by cPanel. So we copied that configuration directive by directive from a cPanel 138 server, and we measured it twice: once exactly as shipped, and once with every performance switch that WHM offers turned on.

The setup

One Hetzner CCX23 — 4 dedicated AMD EPYC vCPUs, 16 GB of RAM, AlmaLinux 9.8 — running CorePanel, with a second, identical machine in the same datacenter generating the load.

The site is the agency profile of wp-test, our public, deterministic WordPress generator: WordPress 7.1.2 with Elementor 4.3.2 and seven other plugins, 30 builder pages and 2,399 widgets, PHP 8.4. No page cache, anywhere.

The web server was the only thing that changed. Apache 2.4 was installed on the same machine and swapped with corehttpd on ports 80 and 443, serving the same files with the same certificate, and — where PHP-FPM was involved — talking to the same PHP-FPM pool:

Apache, as cPanel ships itApache, tuned in WHMcorehttpd
Process modelMPM prefork + mod_ruid2MPM eventevent-driven, one process
PHPCGI, no OPcachePHP-FPM with OPcachePHP-FPM with OPcache
MaxRequestWorkers150150no per-connection worker
Compressionnonemod_deflate (“Compress all content”)gzip
ProtocolsHTTP/1.1HTTP/1.1, HTTP/2HTTP/1.1, HTTP/2, HTTP/3

The first column is what a fresh cPanel server runs: its default EasyApache 4 profile installs mod_ruid2, which requires prefork, and hands PHP to CGI with no OPcache package. KeepAlive On, KeepAliveTimeout 5, MaxKeepAliveRequests 100 and Timeout 300 are cPanel’s values and apply to both Apache columns. The second column is every switch an administrator can flip in WHM without leaving the interface: event MPM, PHP-FPM for the domain, mod_http2, and Optimize Website → Compress all content.

Each visitor gets its own connection, and the load generator asks for bytes the same way from every server. Rates are counted over the time that actually elapsed, and a request that never came back is counted as a failure, not dropped.

A WordPress page

Two bar charts for the same Elementor page with no page cache. Time to build the page for one visitor: corehttpd 191 ms, Apache as cPanel ships it 535 ms, Apache tuned in WHM 183 ms. Pages per second with ten visitors: 14.6, 4.6 and 14.3

Behind Apache as shipped, every page view starts a new php-cgi process that loads PHP, compiles WordPress, Elementor and eight plugins from source, answers, and exits. That is 0.87 seconds of CPU per page, against 0.26 seconds behind corehttpd, where PHP-FPM keeps its workers alive and OPcache keeps the compiled code in memory. On four cores the difference is 4.6 pages per second against 14.6.

Concurrency turns it into memory, because every visitor gets a PHP process of its own:

Line chart of server memory, web server plus PHP, while serving the same WordPress page to 1, 10, 50 and 150 simultaneous visitors. corehttpd uses 281, 352, 361 and 381 MB. Apache as cPanel ships it uses 217 MB, 999 MB, 4.3 GB and 11.1 GB. Apache tuned in WHM uses 231, 348, 348 and 348 MB

With 150 visitors, Apache ran 150 php-cgi processes at once: 11.1 GB for web server and PHP together, on a 16 GB server. corehttpd served the same 150 visitors in 381 MB, sending them through PHP-FPM’s eight workers; the rest waited in line, and every one of them got a page.

Tuned Apache is the control that makes this measurement mean something: pointed at the same PHP-FPM pool, it built the page in 183 ms and served 14.3 pages per second, the same as corehttpd. When PHP does the work, the web server in front of it barely moves the number. What moved it was the way cPanel runs PHP by default.

A new visitor, while others are browsing

This is where the architecture shows. We kept a crowd of visitors on the site, each clicking every 10 seconds, and every half second a new visitor arrived, opened a fresh connection and asked for a file. The question is simple: how long does the new visitor wait?

Horizontal bar chart on a log scale of how long a newly arriving visitor waits for a small file while others browse. With 300 visitors browsing: corehttpd 4.1 ms, Apache as cPanel ships it 363 ms, Apache tuned in WHM 3.3 ms. With 1,000 browsing: corehttpd 3.7 ms, Apache as shipped 18.7 seconds with 12 of 60 new visitors giving up, Apache tuned 1.4 seconds. With 3,000 browsing: corehttpd 3.8 ms, Apache as shipped 19.6 seconds with 40 of 60 getting no page, Apache tuned 7.3 seconds with 19 of 60 getting no page

corehttpd answered in under 5 ms whether 300, 1,000 or 3,000 visitors were connected. It held all 3,000 connections in 381 MB at 2% CPU.

Apache as shipped has 150 processes, and a process that is holding a connection — even an idle one, waiting out KeepAliveTimeout — cannot serve anyone else. With 1,000 visitors browsing, the new visitor queued for 18.7 seconds, and 12 of 60 gave up after 30. With 3,000, only 20 of the 60 got a response at all. The server’s CPU sat at 3% the whole time: it was not busy, it was full.

Event MPM releases the thread while a connection sits idle, which is why tuned Apache matched corehttpd at 300 visitors. At 1,000 it ran into the same MaxRequestWorkers 150 and the wait rose to 1.4 seconds. Raising the limit moves the wall rather than removing it: at 400 workers, the 1,000-visitor case dropped to 3.2 ms, and with 3,000 visitors the median wait was back up at 2.1 seconds. corehttpd has no such number to tune, because it does not assign a worker to a connection.

Static files under a crowd

Line chart of requests per second for a 13.6 KB JavaScript file over HTTP/1.1 as the number of visitors grows. corehttpd serves 20,069, 27,686, 28,194 and 25,581 requests per second at 10, 50, 200 and 1,000 visitors. Apache as cPanel ships it serves 16,759, 22,367, 15,686 and 6,171. Apache tuned in WHM serves 17,042, 23,065, 3,028 and 1,800

For a small JavaScript file with identical bytes from every server, corehttpd led from the first data point — 20,069 requests per second at 10 visitors against 16,759 — and held its line as the crowd grew: 25,581 requests per second at 1,000 visitors, p99 of 107 ms. Apache as shipped fell to 6,171, and its slowest visitor waited 41.6 seconds. Tuned Apache fell off earlier, at 200 visitors, where MaxRequestWorkers 150 meant more active connections than threads; with the limit raised to 400 it served 22,907 requests per second at 200 visitors and dropped to 2,764 at 1,000.

Compression

Browsers ask for compressed responses, and how a server answers is a real part of what it costs to serve a page.

Horizontal bar chart of requests per second for a 140 KB stylesheet requested with Accept-Encoding gzip by 50 visitors. corehttpd 20,446 sending 20.1 KB each; Apache as cPanel ships it 9,263 sending 137.5 KB each because it does not compress; Apache tuned in WHM with compression on 1,307 sending 18.7 KB each

Apache as shipped does not compress at all. mod_deflate is loaded, but no filter is configured; compression is enabled per account under Optimize Website, or by a caching plugin that writes the rules into .htaccess. Out of the box, that 140 KB stylesheet travels uncompressed to every visitor: 137.5 KB on the wire, nearly seven times what it has to be.

Turn compression on, and Apache compresses the file again on every request: 1,307 requests per second. corehttpd compresses each compressible static file — CSS, JavaScript, SVG and the like — once per version and serves the stored copy — 20,446 requests per second, fifteen times more, and at 1,000 visitors still 19,691 against Apache’s 629. The same holds for the small JavaScript file: 21,959 requests per second against 10,046.

What each request costs the server

Throughput says how much a server can take; CPU per request says what every visitor costs while it does. We took the server’s CPU time over each measured window — web server, PHP, database and kernel, everything on the machine — and divided it by the requests answered.

Three bar charts of server CPU time per request served, lower is better. A 13.6 KB JavaScript file with 1,000 visitors: corehttpd 0.15 ms, Apache as cPanel ships it 0.58 ms, Apache tuned in WHM 1.76 ms. A 140 KB stylesheet with gzip and 50 visitors: corehttpd 0.19 ms, Apache as shipped 0.38 ms while sending it uncompressed, Apache tuned 2.30 ms. A WordPress page with 10 visitors: corehttpd 260 ms, Apache as shipped 870 ms, Apache tuned 263 ms

At a comfortable load the three servers are close, with corehttpd ahead: 0.135 ms of CPU for the small file at 50 visitors, against 0.167 ms for Apache as shipped and 0.153 ms tuned. The gap opens under a crowd. At 1,000 visitors corehttpd still spent 0.15 ms per request, while Apache as shipped spent 0.58 ms and tuned Apache 1.76 ms: the extra time goes into juggling connections that are waiting for a worker, so Apache spends more CPU to serve fewer requests.

Compression is where stored copies pay off. corehttpd served the compressed stylesheet for 0.19 ms of CPU; Apache with mod_deflate spent 2.30 ms compressing it again every time, twelve times as much. Apache as shipped spent 0.38 ms — and that is for sending it uncompressed.

On a WordPress page the web server’s share disappears into PHP’s: 260 ms for corehttpd and 263 ms for tuned Apache on the same PHP-FPM pool, 870 ms when PHP runs through CGI.

HTTP/2 and HTTP/3

corehttpd serves HTTP/1.1, HTTP/2 and HTTP/3 on every site, with nothing to enable. Apache as cPanel ships it speaks HTTP/1.1 only: its default profile does not install mod_http2. Switching to event MPM and adding it brings HTTP/2. HTTP/3 is not available in Apache at all.

Where the numbers come from

  • One load generator. Every visitor in these tests arrives from the same machine. That is fine for the tests above, and it is why we left one out: serving a 105 KB image pushes up to 1.2 GB/s through the link between two virtual machines, and from 50 visitors on the numbers describe Hetzner’s network rather than either server.
  • One run per data point, 15 seconds each after a 3-second warm-up (30 seconds for WordPress), with CPU and memory sampled from /proc on the server during the window. Memory is PSS, so pages shared by Apache’s children are not counted twice. CPU per request is the whole machine’s CPU time over the window divided by the requests answered in it.
  • Versions. CorePanel 1.6.20 with corehttpd 1.3.1, Apache 2.4.62 from AlmaLinux AppStream (cPanel ships 2.4.68) with mod_ruid2 0.9.8, the same version cPanel uses. PHP 8.4.26 for every server.
  • What we left out of cPanel’s Apache, all of which would have slowed it down: the splitlogs pipe every access log goes through, mod_security without rules, and mod_bwlimited. Apache also ran without SELinux confinement, as cPanel requires; corehttpd ran confined, as it always does.
  • The fixture and the tools are public. wp-test builds the same site from the same seed on any server; the manifest of this run carries pixels hash 1582e21c6e231bb982d5129b83cdf578. The load generator, benchgen, and the server-side sampler, srvstat.py, live in the same repository’s bench/ directory.

cPanel® is a registered trademark of WebPros International LLC. CorePanel is not affiliated with, sponsored or endorsed by WebPros International LLC.