5.5 Active Health Checks, Plus, and the Open-Source Road
Passive checks learn from traffic. Active checks *ask*. One of them is a product line — the other you can build yourself. A guru knows both, and knows when to pay.
Intermediate
What you will learn
The precise difference between passive and active health checking — and what each catches that the other can't.
NGINX Plus's
health_checkdirective andupstreamAPI — what the money actually buys.The open-source substitutes: probe traffic, log-driven quarantine, external orchestrators — with working patterns.
Session persistence:
sticky cookie/sticky learn(Plus) vs cookie-hash (open source) — the real trade-offs.A decision framework: when Plus pays for itself.
1. Passive vs active — the precise difference
Everything you built in Lesson 5.3 is passive: NGINX infers peer health from real traffic. Three structural blind spots follow:
Blind spot | Why passive can't see it |
|---|---|
The idle dead peer | no traffic → no failures counted → the scheduler still considers it "healthy" until someone pays the first-connect tax |
Semantic health | a peer accepting TCP and answering 200 to
|
Fast recovery | quarantine expiry readmits on probation — recovery is only discovered by risking a real request |
Active health checking closes all three: NGINX (Plus) sends probes on its own schedule, checks response semantics (status, body patterns, latency), and readmits peers the moment they prove health — with zero real traffic at risk.
2. What Plus buys:
health_check and the API
# NGINX Plus syntax (health_check/match are Plus-only — not valid on open-source NGINX)
upstream api-pool {
zone api-pool 64k; # required: shared peer state
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
server {
location /healthz-probe { # internal endpoint the probe hits
proxy_pass http://api-pool;
health_check interval=2s fails=2 passes=2
uri=/health/ready
match=api_ready; # semantic assertion
}
}
match api_ready { # what "healthy" MEANS:
status 200;
header Content-Type = application/json;
body ~ '"state":"ready"'; # regex on the BODY
}Read the power in the parameters:
interval (probe cadence),
fails (trips to mark down — active quarantine),
passes (proofs to readmit),
uri (a
deep health endpoint — DB, queues, warmup — not just
/), and
match blocks: the difference between "TCP answers" and "the service works."
The second Plus pillar is the upstream API:
curl -X POST -d '{"server":"10.0.0.3:8080"}' \
http://localhost:8080/api/6/http/upstreams/api-pool/servers
curl http://localhost:8080/api/6/http/upstreams/api-pool/servers # + live
# state: health, responses, fails, chosen-count per peer — JSON, no reloadAdd/remove/
drain peers at runtime (drain = stop choosing, let in-flight finish — the Lesson 5.1 pattern without the reload). Autoscaling hooks directly into this; Kubernetes users get it via the Ingress Controller's own machinery (Module 14).
Plus also ships the
sticky family:
sticky cookie srv_id expires=1h; (set a cookie naming the chosen peer),
sticky learn (learn the app's own session cookie → peer mapping from responses — zero client visibility), and
sticky route (extract a routing token from the request).
3. The open-source road
None of that is magic, and most of it composes from parts you already own.
Pattern A — probe traffic (the 90 % answer)
Give the pool a health-checking route and let something probe the pool through NGINX:
location = /healthz-all {
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_pass http://api-pool/health/ready; # deep endpoint
}Your monitoring (Prometheus blackbox, uptime checkers, Kubernetes readiness probes in Module 14) hits
/healthz-all every N seconds. Each probe is real traffic: it exercises
proxy_next_upstream (retries past dead peers — keeping their failure counters fresh), refreshes quarantine state, and your
alert becomes "N retries happened in the probe log line" — parseable from
$upstream_addr's comma count, which Module 12 will formalize. Limitations, honestly: probes don't
repair anything themselves (they just keep the passive machinery fed), and
http_5xx retries need care (Lesson 5.3's warning).
Pattern B — log-driven orchestration
The access log's
$upstream_addr/
$status is a health stream (Module 12 makes it structured JSON). A sidecar tails it, applies your own definition of "sick" (error rate, latency percentile, connect-fail count), and acts: flip a peer to
down in a generated include, then
nginx -t + reload (Lesson 1.5's safe ritual) — or remove the peer from your service discovery so the
next config rendering omits it (Module 13 builds exactly this with Consul-Template). You've rebuilt active checking with a log pipeline and a templater — slower to act than Plus (seconds vs sub-second), often fast enough.
Pattern C — DNS as the control plane
With
resolve-flagged peers + a
resolver (Module 13), health decisions move into DNS: your orchestrator removes sick endpoints from the SRV/A records; NGINX follows within
valid= seconds. This is the cloud-native answer — health checking isn't NGINX's job at all, it's the platform's.
Session persistence, open-source edition
upstream sessions {
hash $cookie_srv_id consistent; # the app (or you) sets srv_id
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
map $cookie_srv_id $need_srv_id { # first-visit helper:
"" 1; # no cookie yet?
default 0;
}
server {
location / {
proxy_pass http://sessions;
# app sets the cookie; or NGINX can, with a tiny njs hook (Module 15)
}
}The trade-offs vs Plus
sticky: cookie-hash breaks mapping when the peer set changes (mitigated by
consistent), and there's no
learning mode — but for most session stores it's plenty. (And the Lesson 5.2 verdict applies: prefer the app's own session cookie as the hash key anyway.)
4. When Plus pays for itself
An honest decision framework, not a sales sheet:
Your situation | Call |
|---|---|
Static-ish fleet, deploys by reload, monitoring-driven ops | Open source — this module's toolkit is complete for you |
Autoscaling/k8s at scale, need runtime peer add/remove | Consider Plus (or the F5 Ingress Controller, which bundles it — Module 14) |
"Peers must never receive a real request while broken" (payments, healthcare) |
Plus active checks — the semantic
|
Complex enterprise SSO/entitlement at the edge | Plus (JWT validation built in) or njs (Module 15) |
You want support/SLA on the thing in front of revenue | Plus — that's a procurement decision, not a technical one |
The engineering truth: everything in Lessons 5.1–5.4 is Plus-identical — same engine, same algorithms, same keepalive pools. The delta is operational control (probes, API, stickiness) — real value, specific situations, never speed.
Try it yourself
Everything here runs on open-source NGINX, so today's practice builds the substitute:
Build Pattern A. Add the
/healthz-alllocation above pointing atpool-fail(which has a dead peer), reload, and probe it 5 times:bashfor i in 1 2 3 4 5; do curl -s -o /dev/null -w '%{http_code} ' -H 'Host: lb.example.test' \ http://127.0.0.1:8500/healthz-all; echo; sleep 1 doneWatch the access log: the probe's
upstream_addrshows the dead peer being retried-past, then absent (quarantined), then back (probation) — your "active" checker, feeding the passive machinery.Measure recovery time. With
fail_timeout=10s, count probes until the dead peer disappears fromupstream_addr(first-hit quarantine) and until it returns (probation probe). Those are the SLA numbers your monitoring would report.Design exercise (no code): sketch the log-driven sidecar for your own fleet — which log fields (Module 12 preview:
$upstream_addr,$upstream_status,$upstream_response_time), what rule trips "sick," and what the sidecar does (reload-with-down vs DNS removal). Write it in your notes; Module 13 will implement it for real.
Q&A
Q1. A pool peer receives zero traffic after a deploy rebalances hashing. It dies. What does passive checking do, and what would active checking do?
Answer
Passive: nothing — no traffic means no failures counted; the peer looks healthy until it's chosen again (then one request pays the retry tax and it quarantines). Active: probes continue regardless of traffic; the peer marks down within
interval × fails — no real request ever touches it.
Q2. Your check must fail when the backend's dependency (DB) is down, even though the HTTP listener answers 200. Open source, one location + one app route — sketch it.
Answer
App exposes
/health/ready (checks DB, 503 when down). NGINX:
location /healthz { proxy_pass http://pool/health/ready; proxy_next_upstream error timeout http_503; } — monitoring probes it; failures feed the retry/quarantine machinery. (The semantic body/status assertion is Plus
match's job; the endpoint itself carries that meaning here.)
Q3. Why does the Plus upstream API
require
zone?
Answer
Runtime mutation means the master isn't the only writer of peer state anymore — the API handler (a worker) must read/modify the live list shared with every worker. Shared memory is the only cross-worker blackboard (Lesson 1.4's law); no zone, no shared truth to mutate.
Q4. Compare
hash $cookie_srv_id consistent (OSS) with Plus
sticky learn on: client visibility, mapping durability, and failure behavior.
Answer
Visibility: equal (neither sets its own cookie; both use existing values — OSS needs
someone to set it). Durability: OSS hash breaks on peer-set change (consistent softens);
sticky learn remembers mappings in a shared zone and survives rehashes better. Failure: both lose the session's peer and the app must handle re-auth/repick; Plus tracks it more gracefully. Net: Plus is smoother, OSS is adequate when sessions are cheap.
Takeaways
Passive checks infer from traffic; active checks ask — catching idle-dead peers, semantic sickness, and enabling probe-proven recovery.
Plus buys
health_check+match(semantic probes), the runtime upstream API (needszone), and thestickyfamily — operations, not speed.The open-source road is real: probe routes feeding the retry machinery (Pattern A), log-driven quarantine via templated reloads (B), DNS-as-control-plane (C) — and cookie-hash stickiness covers most session needs.
Choose Plus by operational requirements (autoscale dynamism, never-broken-guarantees, support), never by performance folklore.
Module 5 complete. Your pools schedule fairly, survive failures, reuse connections, and health-check themselves as well as open source allows. Next: Module 6 — the edge gets teeth: TLS termination, certificate operations, cipher hardening, mutual TLS, and access control.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.