cloudflare/pingora
Connection pooling
Pingora keeps idle upstream connections in a pool for reuse, which removes the TCP/TLS handshake cost from subsequent requests. Pooling is enabled by default for HTTP/1 and used differently (and automatically) for HTTP/2.
The user guide page is docs/user_guide/pooling.md.
How it works
Pool storage lives in pingora-pool/src/connection.rs. The HTTP-aware pool (pingora-core/src/connectors/http/mod.rs) is the user of the pool; it wraps it with HTTP-version awareness.
graph TD
proxy[Proxy needs upstream conn]
connector[Connector::get_http_session]
pool{Conn in pool<br/>matching peer hash?}
fresh[TCP/TLS dial]
use[Run request]
keepalive{Keep-alive<br/>allowed?}
release[Pool::release]
drop[Drop connection]
proxy --> connector --> pool
pool -->|hit| use
pool -->|miss| fresh --> use
use --> keepalive
keepalive -->|yes| release
keepalive -->|no| dropA connection is keyed by the peer hash — derived from the destination IP, SNI, TLS settings, client cert, etc. Two requests destined for the same logical upstream share connections.
Configuration
The relevant YAML keys (from docs/user_guide/conf.md):
| Key | Effect |
|---|---|
upstream_keepalive_pool_size |
Total connections to keep in the pool |
client_bind_to_ipv4 / client_bind_to_ipv6 |
Source addresses to bind to when connecting |
The pool is per-runtime (per-service), so a server with two services has two independent pools.
HTTP/1
For HTTP/1, an idle connection sits in the pool until either reused or reaped. Pingora applies a small read on idle connections before checkout to detect "dead" connections — if the server closed the connection without telling us, we'd otherwise hand back a useless socket.
The unexpected data counter (added Mar 2026) increments when this read returns bytes that shouldn't be there — usually a buggy upstream. The counter is exposed for metrics.
The 0.7.0 release also added a configurable cap on how many times a single downstream connection can be reused. After the cap is hit, the proxy doesn't keep the upstream alive for further requests on the same downstream.
HTTP/2
HTTP/2 multiplexes streams over one connection, so the "pool" is more of a "stream registry." The H2 pool tracks "in-use" connections (those with active streams) separately from idle ones, so idle pruning doesn't tear down connections that have streams. The 0.7.0 release fixed an interaction where idle h2 connections weren't properly polled (tweak idle_timeout).
The 0.8.0 release added h2 stream window and connection window configuration keys for tuning flow control.
Custom transports
The "custom" HTTP session (pingora-core/src/protocols/http/custom/) supports encapsulated HTTP transports — HTTP-over-something-not-TCP. Pooling support for custom transports follows the H1 model.
Subrequests and pooling
Subrequests use the same pool as the parent request. The pipe-subrequests utility (added in 0.8.0) lets subrequests reuse the in-flight connection or capture a body for chained subrequests.
Source map
| Concern | File |
|---|---|
| Pool storage | pingora-pool/src/connection.rs |
| HTTP-aware pool | pingora-core/src/connectors/http/mod.rs |
| H2 stream pool | pingora-core/src/connectors/http/v2/ (re-exported via the http module) |
| Per-pool LRU | pingora-pool/src/lru.rs |
| HTTP/1 client session | pingora-core/src/protocols/http/v1/client.rs |
| HTTP/2 client session | pingora-core/src/protocols/http/v2/client.rs |
See also
- pingora-pool
docs/user_guide/pooling.md
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.