Skip to content

RTFM · Healthchecks

lb.saphira.http: ask for a page, read the answer

The first check that speaks the application's language: fetch a URL and demand a sane status code, or demand the body actually contains something. If the web server is up but the application behind it is broken, this is the check that notices.

Saphira Linux dragon mascot

What it proves

A complete HTTP transaction: connect, request, response, status. By default any 2xx or 3xx passes; with a body regex, the response body itself must match. There is no silent degradation; a failed HTTP transaction fails the check, even though a plain TCP connect to the same port might have succeeded.

How it works

curl does the transaction. In status mode (no body regex) the check reads only the HTTP code and matches it against the status-class regex. In body mode it fetches the full body and matches your regex; precise, honest detail: in body mode the status code is not separately re-verified, so write the regex for the page a healthy server actually returns (a body match on an error page will not save you).

Point the check at an application health endpoint (/healthz, /status) rather than the homepage: cheap, deterministic, and no cache in the way.

Configuration

Knobs
VariableDefaultMeaning
LB_SAPHIRA_HTTP_PATH/Path to request
LB_SAPHIRA_HTTP_HOST-Host: header override: essential when the backend serves name-based vhosts
LB_SAPHIRA_HTTP_OK^(2|3)[0-9][0-9]$Status-class regex (status mode)
LB_SAPHIRA_HTTP_EXPECT-Body regex: when set, switches to body mode
LB_SAPHIRA_TIMEOUT3Whole-transaction curl --max-time

Port: $4 (else $2). The -g flag disables curl URL globbing, so literal brackets and braces in paths and IPv6 literals in $3 behave exactly as written.

Wiring it up

ldirectord
# /etc/ha.d/ldirectord.cf
virtual = 203.0.113.10:80
    protocol = tcp
    scheduler = wlc
    real = 192.168.20.11:80 gate 1
    checktype = external
    checkcommand = "/var/lib/lb/saphira/lb.saphira.http"
HAProxy
# haproxy.cfg
global
    external-check

backend app_pool
    mode tcp
    option external-check
    external-check command "/var/lib/lb/saphira/lb.saphira.http"
    server app1 192.168.20.11:80 check inter 5s fall 3 rise 2
Name-based vhosts need the Host knob
# Per-service settings, e.g. /etc/saphira/lb-healthchecks.d/http/web-pool.conf
LB_SAPHIRA_HTTP_PATH=/healthz
LB_SAPHIRA_HTTP_HOST=www.example.com
LB_SAPHIRA_HTTP_EXPECT=READY

Run it by hand

Manual run
LB_SAPHIRA_HTTP_PATH=/healthz LB_SAPHIRA_HTTP_EXPECT=READY \
  /var/lib/lb/saphira/lb.saphira.http web-pool 80 192.168.20.11 80
echo $?
# 0 = body matched (or status was 2xx/3xx in status mode)
# 1 = curl failed, status outside the class, or body did not match

Failure modes

  • curl error (connection refused, timeout, TLS); the transaction never completed
  • Status outside the class: the application answered, badly: 500s surface here, not in tcp
  • Body mismatch: the server answered with the wrong page: vhost routing, stale deploys, WAF block pages
  • exit 2: this check has none; misconfiguration reads as a deterministic 1

Did we miss something?

If this page left something unanswered, found an error, or there is another subject you would like documented, tell us. Saphira’s documentation grows from real problems people need to solve.

Send feedback or request a new section →

Prefer not to do it yourself?

Everything needed to do the work yourself is documented here and remains free; we charge for human time, not for withholding knowledge. Sometimes the missing resource is simply time. The same people who build Saphira can provide paid professional help with implementation, migration, troubleshooting and administration.

Ask about professional support →