# 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.

Source: https://www.corepanel.net/blog/we-had-to-build-our-own-web-server
Author: CorePanel Team
Published: 2026-08-09
Tags: architecture, performance, security, corehttpd
From the CorePanel blog — https://www.corepanel.net/blog

---

> **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:

```json
{
  "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](https://www.corepanel.net/blog/we-measured-the-page-cache).
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](https://www.corepanel.net/blog/elementor-cache-webp-http3) — 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](https://www.corepanel.net/corehttpd) —
protocols, `.htaccess`, the WAF, the privilege model and the questions we get asked.*
