Skip to content

reverseproxy: isolate active health-check state per distinct check config - #7916

Open
SillyZir wants to merge 2 commits into
caddyserver:masterfrom
SillyZir:auto-fix/7870
Open

reverseproxy: isolate active health-check state per distinct check config#7916
SillyZir wants to merge 2 commits into
caddyserver:masterfrom
SillyZir:auto-fix/7870

Conversation

@SillyZir

@SillyZir SillyZir commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #7870.

Multiple reverse_proxy handlers configured with different active health checks (health_uri, health_headers, ...) against the same upstream dial address currently share a single Host in the global pool, so one handler's failing probes mark the address unhealthy for every other handler. This keys the pool by dial address plus a stable fingerprint of the active health-check config, so distinct checks get independent health state.

The fingerprint is strictly internal to pool identity:

  • the Prometheus upstreams_healthy label is untouched (still the plain Dial address);
  • the /reverse_proxy/upstreams admin endpoint strips the fingerprint at emission (hostKeyAddress), so API output reports plain dial addresses as before.

Scope note: dynamic upstreams are intentionally not covered here — they resolve through the separate per-lookup dynamicHosts path with different lifetime semantics, so the same collapse there is left for a follow-up rather than half-fixed in this change.

The regression test (TestActiveHealthChecksSameAddressDifferentChecksAreIndependent) spins up two handlers with different health_uri against one address and asserts vhost B's health state stays independent while vhost A's probes fail; it fails on master and passes with this change.

Files: modules/caddyhttp/reverseproxy/{healthchecks,hosts,reverseproxy,admin}.go, active_health_test.go

Assistance Disclosure
This patch was developed with AI assistance. It was reviewed and tested before submission (affected package tests pass), and is submitted from a human-owned account that takes responsibility for the change.

…nfig

Multiple reverse_proxy handlers configured with different active health
checks (health_uri, health_headers, ...) against the same upstream dial
address currently share a single Host in the global pool, so one
handler's failing probes mark the address unhealthy for every other
handler. Key the pool by dial address plus a stable fingerprint of the
active health-check config, so distinct checks get independent health
state.

The fingerprint is strictly internal to pool identity: the Prometheus
upstreams_healthy label and the /reverse_proxy/upstreams admin endpoint
continue to report the plain dial address, unchanged.

Dynamic upstreams are intentionally out of scope here: they resolve
through a separate per-lookup path (dynamicHosts) and collapsing there
has different lifetime semantics; noted for a follow-up.

Fixes caddyserver#7870
Satisfies the modernize linter; behaviour is unchanged, since Cut returns
the whole string when the separator is absent.

@steadytao steadytao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each handler still writes caddy_reverse_proxy_upstreams_healthy using only upstream.Dial. Distinct health-check states therefore race to update the same Prometheus series so the reported value depends on which updater ran last. The metric identity needs to match the newly separated health-state identity + comment

}
// pool keys may carry an internal health-check fingerprint
// (see hostKeySuffix); report only the plain dial address
address := hostKeyAddress(poolKey)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing consumers that index by address will silently discard all but one. This needs defined aggregation semantics or a public discriminator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reverse_proxy: different active health checks (health_uri/health_headers) against the same upstream address collapse into one shared health state

2 participants