check conditions
A condition is one assertion about a response. Every check runs all of a monitor's conditions and each produces an outcome:
- pass — the assertion held.
- warn — a soft threshold was crossed (continuous metrics only).
- fail — the assertion did not hold.
A check passes only when every condition passes. The monitor's
state is the worst outcome across all
conditions — fail beats warn beats pass.
The conditions list is composable, not pickable. A monitor with
status_code: 200-299 and latency set only passes when both hold;
either one failing fails the whole check.
the conditions
| condition | outcomes | what it asserts |
|---|---|---|
| status_code | pass / fail | response status matches an expected value, range, or list |
| latency | pass / warn / fail | end-to-end response time stays under a threshold |
| body_contains | pass / fail | the response body contains a substring |
| body_not_contains | pass / fail | the response body does not contain a substring |
| body_regex | pass / fail | the response body matches a regular expression |
| header_equals | pass / fail | a response header equals an expected value |
| cert_expiry | pass / warn / fail | the TLS certificate has enough days left |
Discrete predicates (status, body, header) only ever pass or fail —
there's no useful "almost matches". Continuous metrics (latency, cert
expiry) support an optional warn tier in addition to fail, so you
can be told before something becomes an outage.
status_code
Config: expected — a single code (200), a range (200-299), or a
comma list (200,201,204). The default seeded on a new monitor is
200-299.
- type: status_code
config: { expected: "200-299" }
latency
Config: optional warn_ms and optional fail_ms. Set one tier or both.
- below
warn_ms(or no warn set) → pass - between
warn_msandfail_ms→ warn - above
fail_ms→ fail
- type: latency
config: { warn_ms: 500, fail_ms: 2000 }
A warn-only latency tier (warn_ms set, fail_ms empty) turns a slow
endpoint into a degraded monitor that never goes down on latency
alone — useful for an SLO budget you want visibility on without paging.
body matching
Three flavors, all evaluated against the captured body excerpt (the first chunk of bytes the checker buffers — substrings deeper than the excerpt window silently miss):
- body_contains — substring is present.
- body_not_contains — substring is absent.
- body_regex — a regular expression matches.
- type: body_contains
config: { value: "\"status\":\"ok\"" }
Under the hood all three store as the backend's single body_match
condition with a mode of contains, not_contains, or
matches_regex — the form just splits them into three pickable kinds so
each gets the right label and help text.
header_equals
Config: header (name) and value (expected). Header names match
case-insensitively per the HTTP spec; the value matches exactly — no
trim, no case-folding, so trailing whitespace or different casing fails.
An absent header fails. For a multi-value header, it passes if any
value equals the expected string. Cheaper than a body match — no body
bytes are read.
- type: header_equals
config: { header: "Content-Type", value: "application/json" }
cert_expiry
Config: optional warn_days and optional fail_days (the form defaults
to 30 and 7). Evaluated against the leaf certificate from the HTTPS
roundtrip the check already performs — no extra network call.
- type: cert_expiry
config: { warn_days: 30, fail_days: 7 }
cert_expiry requires an https:// monitor URL. Adding it to a
plain-HTTP monitor is rejected at save time
(400 CERT_EXPIRY_REQUIRES_HTTPS). If the TLS handshake itself fails
(broken chain, hostname mismatch, fully-expired cert), the request errors
out first and the monitor fails on that — the condition is never reached.