When an upstream connection fails, Pingora gives you full control over what happens next. You can give up and return an error to the client, retry the same upstream, or — with a small amount of routing logic inDocumentation 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.
CTX — transparently fail over to a completely different upstream. This guide covers how the failure model works, when retries are safe, and how to implement failover with a concrete example.
When Can You Retry?
Whether a retry is safe depends on what has already happened when the failure occurs. Pingora distinguishes two failure points:fail_to_connect() — called when the proxy cannot establish a connection to the upstream at all. At this point, nothing has been sent to the upstream, and the downstream client has received no response bytes. This is the safest possible moment to retry: you have complete freedom to try a different upstream without any risk of double-processing.
error_while_proxy() — called when an error occurs after a connection is established and in use. At this point, the upstream may have already received and partially processed the request. Retrying here is safe only for idempotent HTTP methods (GET, HEAD, OPTIONS, etc.) where processing the request twice has no side effects.
In both cases, retrying is gated on the proxy not having sent any response bytes to the downstream yet. Once the downstream has received data, there is nothing the proxy can do except log and give up.
Making an Error Retryable
To enable a retry, calle.set_retry(true) on the error inside fail_to_connect() or error_while_proxy(). When Pingora sees a retryable error, it calls upstream_peer() again instead of proceeding to fail_to_proxy().
upstream_peer(), your implementation can return the same peer (retry the same upstream) or a different one (failover). The CTX object is the mechanism for communicating which behavior is desired.
Failover Implementation
The pattern is straightforward:- Track the number of attempts in
CTX(e.g., atries: usizefield). - In
fail_to_connect(), increment the counter and sete.set_retry(true)— but only for the first failure. If the secondary also fails, do not retry again. - In
upstream_peer(), check the counter and return a different peer whentries >= 1.
192.0.2.1 and falls over to 1.1.1.1 on the first connection failure:
- First request:
tries = 0, soupstream_peer()selects192.0.2.1. - Connection to
192.0.2.1fails →fail_to_connect()is called.ctx.triesis0, so we increment it to1and sete.set_retry(true).
- Pingora calls
upstream_peer()again. Nowtries = 1 >= 1, so it selects1.1.1.1. - If
1.1.1.1also fails →fail_to_connect()is called again.ctx.triesis1 > 0, so we return the error without setting retry →fail_to_proxy()is called and a 502 is sent.
Retry vs. Failover
These two strategies are closely related but distinct:| Retry | Failover | |
|---|---|---|
| Target | Same upstream peer | Different upstream peer |
| Use case | Transient network hiccup on a reused connection | Primary upstream is unavailable |
| Implementation | Set retry; return the same HttpPeer in upstream_peer() | Set retry; update CTX; return a different HttpPeer in upstream_peer() |
upstream_peer() uses CTX to decide which peer to return on the second call.
Handling error_while_proxy
The default implementation of error_while_proxy() already handles the most common retry case: if the error occurred on a reused connection and the retry buffer has not been truncated (nothing sent downstream), it automatically marks the error retryable. This transparently recovers from stale pooled connections without any code in your implementation.
For custom retry behavior on mid-stream errors, override error_while_proxy():
