An HAProxy backend keeps dispatching to a server whose /healthz probe still returns 200 while real requests to it fail or return 5xx. Which HAProxy server keywords let live traffic itself mark that server down, and what has to be in place for them to take effect?
answer
- let real traffic be the signal
- one keyword to watch, one to count, one to act
- connection errors versus 5xx responses
- it still needs active checks enabled
- a shared fault can eject the whole pool
basics
~20 sHAProxy's observe server keyword watches real traffic instead of probes: observe layer4 counts connection errors, observe layer7 also counts 5xx and malformed responses. error-limit sets how many errors trigger the on-error action, and active checks must be enabled for it to work.
solid answer
~50 sThe mechanism is the `observe` server keyword, paired with `error-limit` and `on-error`. `observe layer4` counts connection-level failures — refusals, resets, connect timeouts. `observe layer7` additionally counts application-level errors, notably 5xx responses and unparseable replies. `error-limit <count>` sets how many consecutive errors trip the reaction, defaulting to 10, and `on-error` chooses what that reaction is: `fastinter` (the default) merely switches to the faster check interval, `fail-check` records one failed health check, `sudden-death` sets the counter one short of `fall` so the next failed check evicts the server, and `mark-down` puts it DOWN immediately. Crucially all of this manipulates the *health-check* state machine, so the server line still needs `check` — `observe` without `check` does nothing. I would pair it with `retries` and `option redispatch` so the request that exposed the fault is retried on a different server rather than lost.
go deeper
Know that HAProxy can react to errors seen on real traffic, not just to its own probes, and that the server keyword for it is observe.
Explain the trio precisely: observe layer4 versus layer7 in what they count, error-limit as consecutive errors, and the four on-error actions — and state that active checks must still be enabled.
Demonstrate the production judgment: pick an on-error action that accelerates verification rather than evicting, size error-limit so a shared-dependency fault cannot empty the pool, and pair the detection with retries plus option redispatch so the failing request is still served.
Own where this responsibility belongs at all — proxy-side passive detection versus a health endpoint that reflects real dependencies versus client-side resilience — and set the standard so no single correlated failure can be amplified into a full backend outage.
## Why the probe can lie An active check tests a synthetic request against a path you chose. Real traffic is different: different URLs, different payload sizes, different code paths, and often a different pool of database connections. A server can therefore serve `/healthz` perfectly while failing everything else — a wedged worker pool that still answers the cheap endpoint, a bad deploy that broke one route, a connection pool exhausted only by the expensive queries. HAProxy's answer is to let the traffic that is already flowing act as the signal. ## The three keywords ```haproxy backend api option httpchk http-check send meth GET uri /healthz retries 3 option redispatch server app1 10.0.0.1:8080 check observe layer7 error-limit 5 on-error mark-down server app2 10.0.0.2:8080 check observe layer7 error-limit 5 on-error mark-down ``` **`observe <layer>`** turns the counting on: - `observe layer4` counts connection-level problems — connection refused, reset, connect timeout, premature close. - `observe layer7` counts those *and* application-level errors on the HTTP response: 5xx status codes and responses HAProxy cannot parse. It requires the proxy to be in HTTP mode, since there is no response status to inspect in TCP mode. **`error-limit <count>`** is how many consecutive errors are tolerated before the `on-error` action fires. The default is 10. A successful request resets the counter, so this measures a run of failures, not a rate over a window. **`on-error <action>`** decides what happens when the limit is reached: - `fastinter` — the default. Switch the check schedule to the `fastinter` interval so the active checker investigates sooner. The gentlest option: traffic errors only *accelerate* verification, they never evict on their own. - `fail-check` — count one failed health check (and switch to `fastinter`). Several rounds of this reach `fall` and the server goes DOWN. - `sudden-death` — set the health counter to one short of fatal, so a single subsequent failed check marks the server DOWN. - `mark-down` — mark the server DOWN immediately, without waiting for a check. ## The requirement people miss Every `on-error` action is expressed in terms of the health-check state machine — accelerating it, adding a failure to it, or forcing its outcome. A server line with `observe layer7` but no `check` therefore has nothing to act on. Enable `check` alongside it, always. ## Choosing the aggressiveness `mark-down` with a low `error-limit` is tempting and dangerous. Consider what happens when the fault is *not* in the servers: a shared database goes read-only, so every server starts returning 500 on live traffic while `/healthz` — which does not touch the database — keeps passing. With `observe layer7 error-limit 5 on-error mark-down`, HAProxy dutifully ejects the entire backend within a few seconds and starts answering 503 itself. HAProxy has no notion of a minimum healthy fraction that must remain in rotation; when the last server goes down the backend fails, unless you have declared `backup` servers to fall back to. That argues for a specific posture: use `observe layer7` with a generous `error-limit` and `on-error fastinter` or `fail-check`, so live errors *bring the active checker's attention forward* rather than deciding the outcome by themselves — and let a well-designed check make the eviction call. Reserve `mark-down` for cases where a per-server fault is clearly distinguishable, and where the health endpoint genuinely cannot detect it. Also remember what `observe layer7` counts: **any** 5xx, including ones the application returns legitimately. An API that answers 503 as a documented back-pressure response, or 500s produced by a client sending malformed input to one particular server, all feed the same counter. There is no way to tell `observe` which statuses to ignore, which is another reason not to wire it straight to eviction. ## Not losing the request that found the fault Detection is only half the outcome. The request that hit the broken server still failed. `retries <n>` tells HAProxy how many times to re-attempt a failed connection attempt, and `option redispatch` allows that retry to go to a *different* server rather than the same one — including overriding a persistence decision that would otherwise pin the client. Without `redispatch`, retries against a dead server just fail three times more slowly. For errors that appear after the connection was established, `retry-on` (HAProxy 2.0 and later) extends retries to layer-7 conditions such as `empty-response`, `response-timeout` or specific statuses — with the usual caveat that re-sending a request the server may already have processed is only safe for operations that tolerate it. ## What to say in an interview Name `observe layer4|layer7`, `error-limit` and `on-error`; state the `check` prerequisite; and show the failure mode you are guarding against — a correlated fault ejecting an entire healthy pool because live traffic errors were wired directly to eviction.
- What does `option redispatch` do that `retries` alone does not?`retries` re-attempts a failed connection, but by default against the same server the balancing or persistence decision already chose — so retrying into a dead node just fails again. `option redispatch` lets HAProxy pick a different server for the retry, overriding a persistence cookie or stick decision when it has to. Together they turn a detected failure into a served request.
- Why can `observe layer7 on-error mark-down` make an outage worse rather than better?Because it cannot tell a per-server fault from a shared one. If a common dependency starts producing 5xx on live traffic while the health endpoint still passes, every server trips its error limit within seconds and HAProxy ejects the entire backend, converting degraded service into total 503. HAProxy has no minimum-healthy floor, so prefer `fastinter` or `fail-check` unless the fault is genuinely per-server.
- Does `observe layer7` let you choose which status codes count as errors?No. It counts 5xx responses and unparseable replies, with no filter. An application that returns 503 as deliberate back-pressure, or 500s triggered by one client's malformed input, feeds the same counter as a genuinely broken server. That lack of selectivity is a strong argument for a generous `error-limit` and a non-evicting `on-error` action.
saying these in an interview costs you the question
- Adds observe to a server line without check and expects it to work
- Wires live 5xx straight to mark-down with a tiny error-limit
- Thinks observe replaces active health checks entirely
- Assumes error-limit counts a rate rather than consecutive errors
- Expects HAProxy to keep a minimum fraction of servers in rotation