Skip to content

Architecture

Pingclair supports HTTP/1.1, HTTP/2, and HTTP/3. Each transport handles protocol I/O, while a shared policy layer applies routing, header rules, rate limits, and access logging. This page describes the components, the request path, and protocol differences in v0.2.2.

🧱 The server is one binary built from a few crates

Section titled “🧱 The server is one binary built from a few crates”

Pingclair is a Cargo workspace. The pingclair binary links the crates below; each one owns a single responsibility.

Crate Responsibility
pingclair Command-line entry point: argument parsing, logging, startup, shutdown, and reload.
pingclair-config Configuration compiler: reads the Pingclairfile, checks it, and produces the configuration the server runs.
pingclair-proxy HTTP/1.1 and HTTP/2 on Pingora, HTTP/3 on quiche, load balancing, and the shared request policy layer.
pingclair-static Static file serving: file reads, MIME types, range and conditional requests, and streaming.
pingclair-fastcgi The FastCGI client that php_fastcgi uses to reach PHP-FPM.
pingclair-tls Certificate management: certificate files, the internal certificate authority, and ACME issuance.
pingclair-api Admin API for inspecting state and reloading configuration.
pingclair-core Data structures and lifecycle shared by the crates above.

🚦 Every request crosses the same policy layer

Section titled “🚦 Every request crosses the same policy layer”
client
|
| TLS with ALPN, or QUIC
v
listener HTTP/1.1 and HTTP/2 on TCP, HTTP/3 on UDP
|
v
transport adapter Pingora ProxyHttp for TCP, tokio-quiche for QUIC
|
v
policy layer routing, matchers, headers, rate limits, access log
|
v
handler file server | reverse proxy | FastCGI | static response
|
v
upstream or disk

The transport adapter converts protocol frames into a request and passes it to the shared policy layer. That layer applies routing, header rules, rate limiting, and access logging across HTTP/1.1, HTTP/2, and HTTP/3. Both transports also reach upstreams through the same connector, so connection pooling, upstream TLS, and timeouts are shared as well.

  • Bodies use bounded memory. Proxy bodies stream by default. Explicit request or response buffering delays forwarding up to its configured ceiling, then streams the remainder; unlimited still caps memory at 8 MiB. Static live compression is bounded as well. See the known streaming defects.
  • Upstream connections are reused. Keepalive connections to backends are pooled. A hostname upstream is resolved again on the interval set by dns_refresh, so a backend container that restarts on a new address is resolved automatically.
  • Configuration is read, never changed, while requests run. Each request reads a published snapshot of the compiled configuration. A reload builds a new snapshot and swaps it in; requests already running finish on the old one.

A few behaviors differ by protocol. They are listed here so that operators can account for them before deployment.

Area Behavior in v0.2.2
Trailers Request trailers are not forwarded on any protocol. A request that declares them is answered 501 before the response starts; an HTTP/3 stream whose response has already started is reset instead. An upstream response that advertises trailers keeps its status and body; the trailer fields are dropped.
CONNECT A usable host:port target receives 405 with Allow; a target without a usable port receives 400. HTTP/1.1 closes after refusal.
FastCGI php_fastcgi works on every protocol, HTTP/3 included.

📌 TRACE also receives 405 with Allow. Malformed HTTP/1 chunked bodies, raw whitespace or controls in request targets, and HTTP/1.1 requests without Host receive 400 and close.

⚠️ WebSocket upgrades fail intermittently under load

Section titled “⚠️ WebSocket upgrades fail intermittently under load”

Pingclair proxies WebSocket, but roughly 10-15% of upgrades fail when the machine is busy. From the outside, a failed upgrade is a connection closed immediately after the 101 Switching Protocols response. The cause is a race in the upstream pingora-proxy crate, not in Pingclair’s upgrade handling, and no configuration avoids it. The failure is less frequent on an idle machine. CHANGELOG.

📌 See Project status for the remaining streaming and protocol defects. Cancelling an HTTP/3 request releases an idle upstream exchange while other streams on the connection remain usable.