TheDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/cloudflare/pingora/llms.txt
Use this file to discover all available pages before exploring further.
ProxyHttp trait is the central API of pingora-proxy. Rather than exposing a single monolithic request handler, it structures request processing as a sequence of named phases — each representing a meaningful moment in the life of a proxied HTTP request. By implementing only the phases you need, you insert custom logic precisely where it matters: before connecting upstream, after receiving a response header, when an error occurs, and so on. Every other phase defaults to a no-op, so you pay only for what you use.
Life of a Request
Every proxied HTTP request passes through up to five major stages:Read downstream request
early_request_filter and request_filter.Connect to upstream
HttpPeer returned by upstream_peer(). This step is skipped when a pooled connection can be reused.Send request upstream
upstream_request_filter fires here, allowing last-minute header manipulation.Duplex proxy
upstream_response_* and response_* filter families run here.Phase Reference
The diagram below shows how phases connect to one another, including retry and error paths:upstream_peer() is the only required method in the ProxyHttp trait. All other phases have default no-op implementations and are optional.General Guidelines
- Most filters return a
pingora_error::Result<_>. When the returned value isErr,fail_to_proxy()is called and the request is terminated. - All filters are
async, allowing IO or other async work inside a phase without blocking other requests. - A per-request
CTXobject is passed to every filter, enabling state sharing across phases within a single request. - Most filters are optional and default to a no-op.
- Both
upstream_response_*_filter()andresponse_*_filter()exist for HTTP caching integration: theupstream_*variants fire before cache writes, the plain variants fire before sending to the downstream.
early_request_filter
early_request_filter
request_filter and before any module (rate limiter, access control, etc.) processes the request.What you can do: Configure module behavior at the finest granularity — for example, enabling or disabling a specific module for this request before it has a chance to run.Return value: Result<()>. An Err terminates the request.Caution: Because this runs before access control and rate-limiting modules, any security-sensitive logic should remain in request_filter() instead, where it will be protected by those modules.request_filter
request_filter
early_request_filter, before any upstream connection is established.What you can do: Parse and validate request headers and paths, apply rate limiting, perform authentication checks, initialize CTX values, or write a response directly and short-circuit the proxy.Return value: Result<bool>. Return Ok(true) if you have already written a response and the proxy should stop; return Ok(false) to continue processing.request_body_filter
request_body_filter
upstream_request_filter, while the request body is being forwarded upstream. Called once per received body chunk — not once for the entire body.What you can do: Inspect or transform each body chunk, throttle upload speed, or run expensive logic (such as WAF rules) on a background thread without blocking the event loop.Return value: Result<()>. An Err terminates the request.proxy_upstream_filter
proxy_upstream_filter
request_filter, before upstream_peer() selects an upstream. This is especially useful when caching is enabled — it lets you defer expensive checks (rate limiting, access control) until after a cache miss.What you can do: Return Ok(true) to continue to the upstream; return Ok(false) if you have already written a response (a 502 is sent by default if you return false without writing a response).Return value: Result<bool>.upstream_peer (required)
upstream_peer (required)
proxy_upstream_filter, and again on each retry.What you can do: Implement any routing logic — DNS lookup, consistent hashing, round-robin, path-based routing, header-based routing, or failover. Return a Box<HttpPeer> that encodes the target address, TLS settings, timeouts, and more.Return value: Result<Box<HttpPeer>>. This is the only phase you must implement.connected_to_upstream
connected_to_upstream
CTX.Return value: Result<()>.fail_to_connect
fail_to_connect
e.set_retry(true) to mark the error as retry-able. If retry is set, upstream_peer() will be called again — at which point your CTX can direct it to a different upstream for failover.Return value: Box<Error> — the (possibly modified) error.upstream_request_filter
upstream_request_filter
Content-Length and upgrade fields.Return value: Result<()>.adjust_upstream_modules (feature-gated)
adjust_upstream_modules (feature-gated)
upstream_compression) run their own header filter. May be called more than once if the upstream sends informational (1xx) headers before the final response.What you can do: Set module-specific configuration based on the response — for example, providing a dictionary for dictionary-based content encoding. Check upstream_response.status.is_informational() to distinguish 1xx headers from the final response.Note: This is an immutable reference to the response header. To modify the response header itself, use upstream_response_filter() instead.Feature flag: Requires the upstream_modules feature.Return value: Result<()>.upstream_response_filter / upstream_response_body_filter / upstream_response_trailer_filter
upstream_response_filter / upstream_response_body_filter / upstream_response_trailer_filter
upstream_response_filter: Add, remove, or modify upstream response headers.upstream_response_body_filter: Inspect or transform each body chunk; return anOption<Duration>to throttle.upstream_response_trailer_filter: Modify upstream trailers.
Result<()> (header/trailers), Result<Option<Duration>> (body — optional throttle delay).response_filter / response_body_filter / response_trailer_filter
response_filter / response_body_filter / response_trailer_filter
upstream_response_* family. These run after caching and are called for every response, including ones served from cache.When they run: Before the response header, body chunks, and trailers are sent to the downstream client.What you can do:response_filter: Final chance to set or remove response headers before the client sees them.response_body_filter: Transform body chunks or throttle delivery.response_trailer_filter: Modify trailers; anOk(Some(Bytes))value is written as body bytes instead.
Result<()> (header), Result<Option<Duration>> (body), Result<Option<Bytes>> (trailers).error_while_proxy
error_while_proxy
Err.What you can do: Inspect the error, add context, and decide whether to retry. The default implementation automatically allows retries on reused connections (where the remote end may have dropped an idle connection) as long as the retry buffer has not been truncated.Return value: Box<Error>.Safety: Only retry idempotent methods (GET, HEAD, etc.) from this phase. If a POST has already been sent to the upstream, retrying may have side effects.fail_to_proxy
fail_to_proxy
FailToProxy (contains error_code and can_reuse_downstream).logging
logging
fail_to_proxy), before resources are released.What you can do: Emit access logs, increment Prometheus counters, flush trace spans, or perform any post-request cleanup. The session.response_written() method reports the status code that was actually sent.Return value: () (infallible — errors here are not propagated).Callbacks
Beyond the phase filters,ProxyHttp provides three callbacks that influence logging behavior:
request_summary()
This callback is invoked whenever an error reaches fail_to_proxy() and needs to be written to the error log. It returns a String that is appended to the log entry, letting you include request-specific context — such as the request ID, user agent, or path — to aid debugging.
suppress_error_log()
Every error that flows through fail_to_proxy() is automatically written to the error log. This callback lets you suppress specific errors — for example, silencing ConnectionClosed errors from downstream clients that disconnect early, which can otherwise flood the log.
Return true to suppress the log entry; false (the default) to allow it.
suppress_proxy_warn_log() (experimental)
Similar to suppress_error_log(), but targets proxy warning logs that do not reach fail_to_proxy() — such as retryable upstream failures or downstream errors ignored while a cache fill continues. The callback receives a ProxyWarnLogContext so you can distinguish these contexts:
