cloudflare/pingora · error

cache_key_callback must be implemented when caching is enabl

Error message

cache_key_callback must be implemented when caching is enabled

What it means

ProxyHttp::cache_key_callback deliberately has no default implementation: a wrong cache key poisons the cache, so pingora refuses to guess and the default body panics with unimplemented!(). The callback is invoked by HttpProxy::proxy_cache as soon as session.cache.enabled() is true, i.e. once your request_cache_filter (or attached cache backend) enabled caching for that request. Hitting this panic means caching was turned on without porting the required key logic.

Source

Thrown at pingora-proxy/src/proxy_trait.rs:191

    /// This callback generates the cache key.
    ///
    /// This callback is called only when cache is enabled for this request.
    ///
    /// There is no sensible default cache key for all proxy applications. The
    /// correct key depends on which request properties affect upstream responses
    /// (e.g. `Vary` headers, custom request filters that modify the origin host).
    /// Getting this wrong leads to cache poisoning.
    ///
    /// See `pingora-proxy/tests/utils/server_utils.rs` for a minimal (not
    /// production-ready) reference implementation.
    ///
    /// # Panics
    ///
    /// The default implementation panics. You **must** override this method when
    /// caching is enabled.
    fn cache_key_callback(&self, _session: &Session, _ctx: &mut Self::CTX) -> Result<CacheKey> {
        unimplemented!("cache_key_callback must be implemented when caching is enabled")
    }

    /// This callback is invoked when a cacheable response is ready to be admitted to cache.
    fn cache_miss(&self, session: &mut Session, _ctx: &mut Self::CTX) {
        session.cache.cache_miss();
    }

    /// This filter is called after a successful cache lookup and before the
    /// cache asset is ready to be used.
    ///
    /// This filter allows the user to log or force invalidate the asset, or
    /// to adjust the body reader associated with the cache hit.
    /// This also runs on stale hit assets (for which `is_fresh` is false).
    ///
    /// The value returned indicates if the force invalidation should be used,
    /// and which kind. Returning `None` indicates no forced invalidation
    async fn cache_hit_filter(
        &self,

View on GitHub (pinned to 0046038bd4)

Solutions

  1. Override cache_key_callback in your ProxyHttp impl and return a key built from every request property that changes the upstream response (host, scheme, path, query, Vary-relevant headers)
  2. Model the override on pingora-proxy/tests/utils/server_utils.rs:697 (host + path_and_query) and extend it for your request filters
  3. If caching was enabled unintentionally, stop enabling it in request_cache_filter so session.cache.enabled() stays false

Example fix

// before: caching enabled in request_cache_filter but no cache_key_callback override
impl ProxyHttp for MyProxy {
    fn request_cache_filter(&self, session: &mut Session, _ctx: &mut ()) -> Result<()> {
        // ...enables caching via session.cache...
        Ok(())
    }
}

// after: add the required override
fn cache_key_callback(&self, session: &Session, _ctx: &mut Self::CTX) -> Result<CacheKey> {
    let req = session.req_header();
    let host = req.headers.get(http::header::HOST).and_then(|v| v.to_str().ok()).unwrap_or("");
    let pq = req.uri.path_and_query().map(|p| p.as_str()).unwrap_or("/");
    Ok(CacheKey::new(format!("{host}{pq}"), String::new()))
}
Defensive patterns

Strategy: validation

Validate before calling

// CI guard: drive one cacheable request through your ProxyHttp impl.
// The default cache_key_callback panics, so this fails at test time, not in prod.
#[tokio::test]
async fn cacheable_request_produces_cache_key() {
    // enable caching exactly like request_cache_filter does, then issue a
    // request through the proxy harness (see pingora-proxy/tests/utils/server_utils.rs)
    // and assert a normal response instead of a panic/connection reset.
}

Prevention

When it happens

Trigger: A ProxyHttp implementation enables caching for a request (request_cache_filter interacts with session.cache to enable it, proxy_trait.rs:161-172) but does not override cache_key_callback. The first cacheable request reaches proxy_cache.rs:56 and panics inside the default callback, killing that request task.

Common situations: Copying a proxy example and switching on the cache backend without porting the key callback; enabling cacheability per-request from config so tests on non-cacheable routes miss it; upgrading pingora where the reference implementation now lives in pingora-proxy/tests/utils/server_utils.rs.

Related errors


AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16). Data as JSON: /api/errors/b68ea0e789c895ae. Report an issue: GitHub.