← Blog

We had to build our own web server

CoreHttpd runs on an engine that spent six years in front of cPanel servers, protecting more than 800,000 domains — and it never runs as root.

Two towers side by side on the same floor: on the left a cold grey stack of six mismatched slabs held together by looping cables, with visible gaps between every layer, and on the right a single seamless block of the same height lit from within in bright blue with an emerald glow along its base

In short: CoreHttpd is CorePanel’s own origin web server. Its engine is not new — it is the same one behind pxShield, our WAF product, which spent six years in front of Apache on cPanel servers and has protected more than 800,000 domains. What is new is that it is now the origin instead of a proxy. It serves HTTP/2 and HTTP/3, learns and emits Early Hints on its own, converts images to WebP without a plugin, serves WordPress in 1.6 ms without touching PHP — and it does all of that without ever running as root.

Every hosting panel ships someone else’s web server. cPanel ships Apache and spends enormous effort generating configuration for it. Others ship nginx, or LiteSpeed, and do the same thing with different templates. We did that too, for years, and it is worth explaining exactly why we stopped.

A panel does not configure a web server. It generates one.

This is the part that nobody says out loud. When you create an account in a hosting panel, no human writes a server block. A program does — a template renders a text file, another program validates it, a third reloads the daemon, and everyone hopes the result means what it looked like it meant.

That is a strange way to run software. The web server’s configuration language was designed for an administrator with a text editor and an intention. In a panel there is no administrator and no intention: there is a database row, and a renderer trying to express it in a language built for someone else. Every panel bug you have ever hit on a domain — the redirect loop, the alias that silently shadowed a subdomain, the PHP handler that pointed at the wrong pool — lives in that translation gap.

CoreHttpd removes the gap. CorePanel writes a JSON site file and CoreHttpd serves it:

{
"domain": "example.com",
"aliases": ["www.example.com"],
"docRoot": "/home/example/public_html",
"tryFiles": ["$uri", "$uri/", "/index.php?$query_string"],
"conf": { "id": 1, "uid": 1001,
"phpfpm": { "network": "unix", "address": "/run/php-fpm/example.sock" } }
}

There is no text templating and no parsing step. Site files are written atomically — written to a temporary name and rename()d into place — so a half-written file is never read as a live configuration, and a SIGHUP reloads everything with no downtime and no dropped connections. For anything richer than a single docroot there is an ordered, Caddy-style route table with reverse_proxy, per-path roots, prefix stripping, redirects and static responses.

The engine is six years old. Only its posture is new.

CoreHttpd is built on pxshield-base, the same engine that powers pxShield, our commercial WAF. pxShield sits in front of an existing web server — it intercepts traffic via netfilter DNAT and discovers sites by parsing that server’s configuration. It has been doing that on cPanel servers since 2020, and more than 800,000 domains have passed through it in those six years.

That is not six years of friendly traffic. A WAF’s entire job is to be attacked. The parts of a web server that are genuinely dangerous to write yourself — HTTP parsing, TLS termination at scale, connection handling, protocol edge cases — are exactly the parts that have been hardened by six years of people trying to break them on purpose, on domains we did not control, in an environment we did not own.

What CoreHttpd changes is posture. pxShield fronts someone else’s server; CoreHttpd is the server. It binds :80 and :443 directly, serves static files from disk, and speaks FastCGI to per-user PHP-FPM pools. The engine underneath is the same one, in the same process model, doing the same job it has always done — with the Apache that used to sit behind it deleted.

We spent years protecting other people’s panels. Then we built our own.

It does not run as root

Apache runs as root. So does nginx, so does LiteSpeed, and so does pxShield itself — it has to, because it writes cached objects into user home directories and chowns them.

CoreHttpd runs as a dedicated unprivileged corehttpd user. Not “drops privileges after binding” — it never has them. It gets exactly two capabilities: CAP_NET_BIND_SERVICE to bind the low ports, and CAP_DAC_READ_SEARCH, which is read-only, to read document roots it does not own.

The consequence is worth stating plainly, because no other hosting panel can state it: a compromised CoreHttpd cannot read your customers’ wp-config.php. Those files are 0600, owned by the account, and only that account’s PHP-FPM pool ever reads them. CoreHttpd is the HTTP front and nothing more — it never executes user PHP in-process, which is precisely the property that single-process PHP servers give up.

That read capability also let us delete an entire category of hosting bug. Panels traditionally give the web server access to user files with POSIX ACLs, which tar, rsync, backups, restores and ordinary uploads all quietly strip — so the panel has to keep reapplying them forever. CoreHttpd needs no ACLs at all. Homes stay 0700, files stay 0600, and the server reads them anyway.

And because that capability bypasses discretionary permissions, the real boundary is mandatory access control: CoreHttpd runs confined in its own SELinux domain, corehttpd_t, shipped as a policy module with the RPM. Not httpd_t — its own, scoped to exactly what it does. This is the opposite of the industry norm; the standard cPanel troubleshooting advice has been “disable SELinux” for over a decade. CorePanel ships a policy instead, and expects enforcing mode.

Early Hints that nobody has to configure

HTTP 103 Early Hints let a browser start fetching your CSS and fonts while the server is still building the page. It is one of the largest real-world wins available on the modern web, and essentially nobody in shared hosting has it — because using it normally means an engineer listing the right sub-resources per page, by hand.

CoreHttpd learns them. The engine watches what a page actually preloads, stores those sub-resources keyed by site and section, and replays them as a 103 on the next request. Hints carry a TTL, so they repair themselves after an asset deploy. Expiry is stale-while-revalidate rather than a hard cutoff, so the first visitor after an idle night still gets a 103 and the very next response refills the bucket. It is on by default, it is inert on static sites and SPAs where it would buy nothing, and a site can opt out.

Nobody configures anything. That is the whole point — and it is only possible because the component that learns the hints and the component that serves the page are the same program.

Everything else lives in the same request, on purpose

This is the argument for building the server rather than assembling one. These features are not independent, and when they live in separate third-party modules they break at the seams:

  • The page cache and the WAF have to agree on order. Cache in front and you serve uninspected content; inspect every hit and the cached page stops being fast. That is a product decision, and with bolted-on modules you do not get to make it — load order does.
  • WebP conversion has to agree with the cache. Conversion depends on what the client accepts; a cache that does not understand that will happily serve WebP to a browser that cannot read it. Same engine, one decision, no seam.
  • Early Hints has to agree with the protocol and the upstream. 103 only makes sense on H2/H3, on an HTML response that took long enough to be worth it.
  • The WAF has to see the connection, not just the headers. Browser fingerprinting is worthless if your WAF is a module that only ever sees a parsed request.

The same integration is why the dynamic page cache is safe enough to leave on. It never guesses what is cacheable: our WordPress companion declares in the response what may be stored, and the engine stores only that. On a 2-vCPU box, a WordPress home page went from 248 ms to 1.6 ms and from 12 pages per second to 5,507, with pages that cannot be shared correctly left untouched — full method and numbers. On a much heavier Elementor site hit by 400 simultaneous visitors, the same size of box delivered 78,946 pages in twenty seconds instead of 251, answered every visitor in 0.09 s, and finished the run 9% idle — which is worth saying plainly: in neither test did we find the ceiling. We ran out of load generator and out of vCPUs, not out of server.

On top of that: HTTP/3 over QUIC on by default, advertised via Alt-Svc. WebP conversion on the fly with no plugin and no build step. A per-account cache budget that can size itself from the account’s disk quota. ACME certificates issued and stored by the server itself. And the WAF — the same one that protected those 800,000 domains commercially — included in all three editions, including the free one.

One static binary

CoreHttpd ships as a single statically linked binary that runs on any RHEL 8 or 9 without matching shared libraries, built with pure-Go user and DNS resolution so it has no glibc runtime dependency at all. Compare that to the EasyApache model, where a panel compiles a web server, its module zoo and their dependencies onto every customer’s machine and then owns the consequences forever.

Install the RPM, and the web server is a file.


We did not set out to write a web server. We set out to make hosting fast and safe, and for six years we did it from the outside — bolted to the front of somebody else’s stack, fixing what that stack would let us reach. Eventually the list of things we could not fix from there got longer than the list we could. The engine was already written, already attacked, already proven. All that was left was to stop asking permission.

What CoreHttpd does, in one page: the web server inside CorePanel — protocols, .htaccess, the WAF, the privilege model and the questions we get asked.