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.

note

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

conditionoutcomeswhat it asserts
status_codepass / failresponse status matches an expected value, range, or list
latencypass / warn / failend-to-end response time stays under a threshold
body_containspass / failthe response body contains a substring
body_not_containspass / failthe response body does not contain a substring
body_regexpass / failthe response body matches a regular expression
header_equalspass / faila response header equals an expected value
cert_expirypass / warn / failthe 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_ms and fail_mswarn
  • above fail_msfail
- type: latency
  config: { warn_ms: 500, fail_ms: 2000 }
tip

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 }
warning

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.