Modules
Each module is a self-contained check that knows what “healthy” means for one kind of target. 32 modules ship today; the backlog tracks what’s next.
| Module | Checks | What it tells you |
|---|---|---|
certs |
TLS certificates | Certificate expiry for explicit targets and every host of an Ansible inventory, with WARN/BAD day thresholds. |
http |
HTTP endpoints | HTTP probes with an expected status code, a maximum latency, and an expected body substring. |
nats |
NATS JetStream | Cluster health from /varz and /jsz — meta-leader present, offline or lagging peers, ghost peers, mixed versions. |
haproxy |
HAProxy | Backend and server state from the CSV stats export — servers DOWN/MAINT/DRAIN, backends with no available server, session saturation. |
stream |
HLS / DASH streams | Manifest reachable and valid, a complete bitrate ladder, and a fresh live edge. |
patroni |
Patroni PostgreSQL | Cluster topology via the Patroni REST API — a single leader, replica state, replica lag, timeline divergence. |
consul |
HashiCorp Consul | Raft leader and quorum, critical/warning service checks, and required KV keys. |
postgres |
PostgreSQL | Read-only SQL checks — transaction-ID wraparound risk, connection saturation, inactive replication slots, replica lag. |
dns |
DNS | Records resolve, no drift from the expected answers, SOA-serial and answer consistency across resolvers, TTL floor. |
redis |
Redis / Valkey | Reachability and role, memory against maxmemory, replication link and lag, RDB/AOF persistence. |
keycloak |
Keycloak | Health endpoint UP and per-realm OIDC discovery — token endpoint present, issuer coherent. |
tcp |
TCP services | Generic TCP reachability with optional TLS, connect latency, and an optional banner match. |
tls |
TLS handshakes | Deep TLS inspection — chain validity, leaf expiry, hostname mismatch, weak negotiated protocol version. |
pq |
Post-quantum TLS readiness | Which classes of TLS client can still handshake — and whether the others are declined with an alert or cut off mid-hello, which is the difference between a setting and an outage. |
ntp |
NTP / clock drift | Clock offset and stratum over SNTP, because drift silently breaks TLS and JWT validation. |
rabbitmq |
RabbitMQ | Nodes running and alarm-free, queue depth thresholds, and backlogs with no consumer attached. |
grpc |
gRPC services | The gRPC Health Checking Protocol over HTTP/2 and TLS — SERVING, NOT_SERVING or UNKNOWN. |
ldap |
LDAP directories | Connect, bind (anonymous or with credentials from the environment), and an optional sanity search. |
kafka |
Apache Kafka | Controller present, broker count, under-replicated partitions, and consumer-group lag. |
ingest |
RTMP / SRT ingest | Can a streamer actually publish? RTMP handshake over TCP or SRT induction over UDP, with connect latency. |
s3 |
S3 object storage | Bucket reachable and an optional sentinel object present and fresh, signed with hand-rolled AWS SigV4. |
smtp |
SMTP relays | Connections accepted, 220 greeting, EHLO, optional STARTTLS or implicit TLS and relay certificate expiry — never sends mail. |
elasticsearch |
Elasticsearch / OpenSearch | Cluster health green/yellow/red, unassigned shards, expected node count, per-node disk watermark. |
mongodb |
MongoDB | Replica-set status — primary present, member health, secondary lag — and connection saturation. |
mysql |
MySQL / MariaDB | Reachability, read-only role, connection saturation, replica IO/SQL threads and lag. |
etcd |
etcd | /health, a leader present (quorum held), and member count against the expected size. |
clickhouse |
ClickHouse | /ping and SELECT version(), plus read-only replicated tables and replication delay. |
vault |
HashiCorp Vault | Seal status (sealed or uninitialized), active/standby role, and the running version. |
memcached |
memcached | Reachability, memory against limit_maxbytes, evictions since startup, connections and version. |
cassandra |
Cassandra / ScyllaDB | Nodes accept CQL via the native-protocol handshake, handshake latency, and cluster size against the expected node count. |
mediamtx |
mediamtx streaming server | Paths present and ready via the v3 control API, expected paths missing, stalled ingest (ready but zero bytes received), readers per path. |
flow |
Multi-step HTTP flows | Ordered HTTP steps where a value captured from one response is used by the next; the finding names the step that failed. Declarative only, captured values never printed. |
Each module also has its own page —
/modules/<name>— with the same detail on one screen: certs, http, postgres, kafka, and so on for all 29.
certs — TLS certificates
TLS certificate expiry across a fleet.
- Dials each target with SNI and reads the leaf certificate’s
NotAfter. - Reports
OK,WARN(expires withinwarn_days), orBAD(withincrit_daysor already expired). - A dial/handshake failure is
ERROR(couldn’t measure), notBAD. - Targets come from the explicit
targetslist and/or every host of an Ansible INI inventory (ansible_inventory). Probes run concurrently.
The dial uses
InsecureSkipVerifyon purpose: we want the expiry date even when the chain doesn’t validate locally. It is an expiry reader, not a chain validator.
See Configuration → checks.certs.
http — HTTP endpoints
HTTP endpoint probes.
- Checks the response status against
expect_status(mismatch →BAD). WARNwhen the response is slower thanmax_latency_ms.BADwhenexpect_bodyis set and its substring is missing.- A network/transport error is
ERROR.
See Configuration → checks.http.
nats — NATS JetStream
Preflight/health of a NATS JetStream cluster, read from each node’s HTTP
monitoring port (/varz and /jsz?meta=1) — the read-only endpoints only, it
never mutates the cluster. It encodes the operational signals from the ops
runbook:
- Reachability + version per node (
OKwithserver_name, version, conns, uptime;ERRORif the monitoring port doesn’t answer). - Mixed versions across the cluster →
WARN(e.g. mid-upgrade skew). - Meta-leader:
BADif no meta-leader is elected (quorum lost),WARNif the elected leader disagrees across nodes, or if it isn’t theexpect_meta_leaderyou configured. - Peer health (from the meta raft group):
BADif a peer isOFFLINE,WARNifnot current. - Peer lag:
WARN/BADwhen a peer’s raft lag crosseslag_warn/lag_crit. - Ghost / missing peers: with
expect_peersset, an unexpected member is aWARN(ghost), an expected member absent from the cluster isBAD.
See Configuration → checks.nats.
haproxy — HAProxy
Backend/server health from the HAProxy CSV stats export over HTTP (the
;csv stats endpoint) — read-only, it never mutates HAProxy.
- Per server:
UP→OK,DOWN→BAD,MAINT/DRAIN/NOLB→WARN. - Per backend (the
BACKENDaggregate row):DOWN→BAD(no server available). - Optional session saturation: with
session_warn_pct, a server/backend at or above that percent of its session limit (scur/slim) isWARN. - An unreachable stats page is
ERROR. Frontends are skipped to keep the output signal-dense.
Findings are labelled backend/server (e.g. web/web2). Optional HTTP basic
auth is supported, with the password read from an env var — never stored in the
config.
See Configuration → checks.haproxy.
stream — HLS / DASH streams
HLS and DASH stream health, read from the manifest — it fetches only manifests, never media segments.
- Reachability / validity: an unreachable manifest is
ERROR; a manifest that doesn’t parse (bad.m3u8/.mpd) isBAD. - Ladder completeness: with
min_variants, a master playlist (HLS) or MPD (DASH) with fewer renditions isWARN, with none isBAD. - Live-edge freshness (when
live: true): the age of the live edge — from HLS#EXT-X-PROGRAM-DATE-TIMEadvanced by segment durations, or DASHpublishTime— isWARN/BADpastmax_age_warn_seconds/max_age_crit_seconds. Ifliveis set but the manifest is VOD (HLS#EXT-X-ENDLIST, or a static MPD), that’s aWARN. - Freshness needs a timestamp in the manifest: an HLS live playlist without
#EXT-X-PROGRAM-DATE-TIMEreportsWARN(“not measurable”) rather than a false OK.
For an HLS master playlist with live: true, the check fetches the
highest-bandwidth variant to measure its live edge. Findings are labelled
name, name [ladder], name [live-edge]. Format (HLS vs DASH) is detected
from the .mpd extension, the dash+xml content-type, or an <MPD root.
See Configuration → checks.stream.
patroni — Patroni PostgreSQL
Health of a Patroni-managed PostgreSQL cluster from the Patroni REST API
(/cluster) — read-only, it never touches PostgreSQL itself.
- Leader: exactly one expected. Zero leaders →
BAD(failover in progress or lost quorum); more than one →WARN(split-brain). - Replica state:
running/streaming→OK;stopped/start failed/crashed/restarting→BAD; anything else →WARN. - Replica lag:
WARN/BADpastlag_warn_bytes/lag_crit_bytes. A lag reported asunknownisOKwith a note (not a false alarm). - Timeline: a replica on a different timeline than the leader →
WARN.
Any Patroni node proxies the same cluster view, so listing one endpoint is
enough; extra endpoints add redundancy (an unreachable one is ERROR). The
cluster-level leader finding is labelled with the cluster scope; per-node
findings are labelled by member name.
See Configuration → checks.patroni.
consul — HashiCorp Consul
Consul cluster health via the HTTP API — read-only.
- Raft leader:
/v1/status/leaderempty →BAD(no leader elected). - Raft peers:
/v1/status/peerscount vsexpect_peers— below quorum (majority) →BAD, below expected but with quorum →WARN. - Service/node health: every check in
/v1/health/state/critical→BAD, every one in.../warning→WARN, labelledservice@node. - KV keys: each key in
kv_keysmissing (/v1/kv/<key>→ 404) →BAD.
Any agent answers cluster-wide, so one endpoint is enough; extras add
redundancy (an unreachable one is ERROR). An ACL token can be supplied via
token_env (read from the environment, never stored in config).
See Configuration → checks.consul.
postgres — PostgreSQL
PostgreSQL health via read-only SQL (using the pgx driver — the module’s
own dependency). It never runs DDL or writes.
- Reachability: a failed connect or query is
ERROR; otherwiseOKwith the role (primary/replica, frompg_is_in_recovery()). - Transaction wraparound:
max(age(datfrozenxid))pastwraparound_warn_age/wraparound_crit_age→WARN/BAD(wraparound looms near ~2.1e9). - Connection saturation:
WARNwhen active connections reachconn_warn_pct% ofmax_connections. - Inactive replication slots: an inactive slot is
WARN; if it retains WAL pastslot_warn_bytes/slot_crit_bytes→WARN/BAD(disk-fill risk). - Replica lag (primary only, from
pg_stat_replication):WARN/BADpastlag_warn_bytes/lag_crit_bytes.
Findings are labelled name, name [wraparound], name [connections],
name [slot:<name>], name [repl:<client>]. The password is read from the
target’s password_env — never stored in the config.
See Configuration → checks.postgres.
dns — DNS
DNS resolution health, using a small in-tree DNS client (no third-party dependency) so it can query specific resolvers and read TTLs and SOA serials.
- Resolution: a name that no resolver answers is
ERROR; a name that resolves to no record of the requested type isBAD. - Drift: with
expect, an answer set different from the expected values isBAD(forSOAthe serial is compared). - Consistency across resolvers: when resolvers return different answers —
including divergent SOA serials (a propagation lag) — that’s
WARN; so is a resolver that fails to answer while others succeed. - TTL: with
min_ttl_seconds, an answer TTL below the threshold isWARN.
Supported record types: A, AAAA, CNAME, NS, TXT, SOA. Resolvers
default to the system /etc/resolv.conf when none are configured. Findings are
labelled name/TYPE, name/TYPE [consistency], name/TYPE [ttl].
See Configuration → checks.dns.
redis — Redis / Valkey
Redis / Valkey health via a minimal in-tree RESP client (no third-party
dependency) reading INFO — read-only commands only.
- Reachability: a failed connect/
PING/INFOisERROR; otherwiseOKwith version and role.WARNwhile the dataset is stillloading. - Memory: with
mem_warn_pctand a configuredmaxmemory,used_memoryat or above that percent isWARN. - Replication (replicas):
master_link_statusnotup→BAD; the master/replica offset lag pastlag_warn_bytes/lag_crit_bytes→WARN/BAD. - Persistence: a failed last RDB bgsave, or AOF last write when AOF is
enabled, →
WARN.
Findings are labelled target, target [memory], target [replication],
target [persistence]. TLS (rediss) and ACL auth are supported; the password
is read from password_env, never stored in config.
See Configuration → checks.redis.
keycloak — Keycloak
Keycloak health via HTTP/JSON — read-only, no admin credentials.
- Health (when
health_urlis set): the endpoint (e.g./health/ready, often on the management port) must reportstatus: UP→OK;DOWN→BAD; unreachable →ERROR. - Per realm: the OIDC discovery document
(
/realms/<realm>/.well-known/openid-configuration) must return200with atoken_endpoint→OK. A404/invalid document →BAD(realm missing); anissuerthat doesn’t end with/realms/<realm>→WARN(usually a proxy/frontend-URL misconfiguration); unreachable →ERROR.
Findings are labelled health and realm/<name>.
See Configuration → checks.keycloak.
tcp — TCP services
Generic TCP reachability for anything that speaks TCP.
- Connects to each
address(optionally over TLS) and measures the connect latency. A failed connect isERROR. - With
expect_banner, the first bytes the server sends must contain the string, elseBAD(e.g.SSH-2.0on port 22). - With
max_latency_ms, a slower connect isWARN.
See Configuration → checks.tcp.
tls — TLS handshakes
Deep TLS check — complements certs (which only reads leaf expiry).
- Chain: verifies the presented chain against the trust store (with the
hostname); invalid (untrusted, hostname mismatch) →
BAD. - Expiry: leaf days-to-expiry →
OK/WARN/BAD(warn_days/crit_days). - Protocol: the negotiated version; below TLS 1.2 →
WARN(connects permissively down to TLS 1.0 just to observe and flag it). - Unreachable / handshake failure →
ERROR.
Findings are labelled target [chain], target [expiry], target [protocol].
See Configuration → checks.tls.
pq — Post-quantum TLS readiness
Which classes of TLS client can still complete a handshake with an endpoint, now
that hybrid post-quantum key exchange (X25519MLKEM768) is on by default in
Chrome, Edge, Firefox, Go 1.24+, OpenSSL 3.5+ and several CDNs. It embeds
pqprobe rather than reimplementing it:
the distinction the check rests on has to live in one place.
That distinction is the point of the module. A peer that answers a post-quantum ClientHello with a TLS alert parsed it and declined a group — a policy, a pinned group list, a negotiation that worked. A peer that resets, times out or vanishes choked on the hello itself, and is broken for every client that so much as offers ML-KEM, however happy that client would have been with X25519. Only the second is an outage waiting for a CDN to flip a default, and no other check here reports the difference.
pq-ready→OK: hybrid key exchange works, including for a client that requires it.pq-blind→WARN: no ML-KEM, but capable clients still connect on a classical group.pq-refusing→BAD: capable clients are declined with an alert while classical ones connect — look at the configuration.pq-intolerant→BAD: capable clients are cut off while classical ones connect — look at the path: a middlebox, an old TLS library, a load balancer that reads the hello.no-tls13→WARN: TLS 1.2 is the ceiling, so post-quantum is out of reach; a ceiling, not a setting.unreachable/tls-broken/mtls-required→ERROR: not a grade at all.
A healthy endpoint is one row. A failing one keeps its evidence: the verdict plus the handshake that produced it, which together are the argument somebody takes to a CDN vendor.
Targets accept the same forms pqprobe does, including 1.2.3.4=origin.example
to dial an address while sending a server name — the only way to reproduce a
CDN-only failure from here, and the way to find the one node out of six that is
broken.
Read-only: TLS handshakes, closed immediately. No request, no body, no credentials — there is nothing it could change on the far side.
See Configuration → checks.pq.
ntp — NTP / clock drift
NTP clock-offset check via a hand-rolled SNTP query (UDP, zero dependency). Clock drift silently breaks TLS validation and JWT expiry.
- Estimated clock offset past
offset_warn_ms/offset_crit_ms→WARN/BAD. - An unsynchronized server (stratum 0 kiss-o’-death, or ≥16) →
BAD. - Unreachable / no reply →
ERROR.
See Configuration → checks.ntp.
rabbitmq — RabbitMQ
RabbitMQ health via the management HTTP API — read-only.
- Reachability:
/api/overview(basic auth) →OKwith version, elseERROR. - Nodes (
/api/nodes): not running, or a memory / disk-free alarm →BAD. - Queues (
/api/queues): depth pastqueue_warn_depth/queue_crit_depth→WARN/BAD; messages present with zero consumers →WARN(stuck backlog).
Any management node answers cluster-wide, so one endpoint is enough. Findings
are labelled node/<name> and queue/<vhost>/<name>. The password is read from
password_env, never stored in config.
See Configuration → checks.rabbitmq.
grpc — gRPC services
gRPC Health Checking Protocol (grpc.health.v1.Health/Check) over HTTP/2 +
TLS, with the protobuf messages encoded by hand — no gRPC library dependency.
(Plaintext h2c isn’t supported; TLS endpoints only.)
SERVING→OK;NOT_SERVING/SERVICE_UNKNOWN→BAD;UNKNOWN→WARN.grpc-status 12(UNIMPLEMENTED, no health service) →WARN;5(NOT_FOUND, unknown service) →BAD.- Connection/handshake failure →
ERROR.
Set service to check a specific gRPC service, or leave empty for whole-server
health. insecure_skip_verify for internal self-signed endpoints.
See Configuration → checks.grpc.
ldap — LDAP directories
LDAP directory health via bind + an optional sanity search (uses go-ldap).
- Connect failure →
ERROR; failed bind (bad credentials) →BAD. - With
base_dn, a search returning fewer thanmin_entries(default 1) or an error →BAD. ldaps://andstart_tlssupported;insecure_skip_verifyfor internal self-signed. Bind is anonymous whenbind_dnis empty; the password comes frompassword_env, never config.
See Configuration → checks.ldap.
kafka — Apache Kafka
Kafka cluster health via franz-go/kadm (admin metadata only).
- Metadata unreachable →
ERROR; no controller →BAD; fewer thanexpect_brokers→WARN. - Any under-replicated partition (ISR < replicas) →
BAD. - Per consumer group in
groups: total lag pastlag_warn/lag_crit→WARN/BAD.
Findings: cluster, partitions, group/<name>. Optional TLS and SASL
(plain/scram); the SASL password comes from sasl_password_env, never config.
The Kafka I/O is behind an interface, so the finding logic is unit-tested with
a fake — no real broker in tests.
See Configuration → checks.kafka.
ingest — RTMP / SRT ingest
Answers “can the streamer publish?” by speaking just enough of each protocol to prove a real server is listening, not just an open port:
- RTMP (
protocol: rtmp, default): the RTMP simple handshake over TCP (C0/C1 → S0/S1/S2, version checked). - SRT (
protocol: srt): the SRT induction handshake over UDP (a best-effort reachability probe — it confirms an SRT listener answers).
OK on a completed handshake, WARN over max_latency_ms, ERROR if the
connection or handshake fails, BAD on an unknown protocol. Zero dependencies;
tested against in-test fake RTMP/SRT servers.
s3 — S3 object storage
Checks an S3-compatible bucket (AWS S3, MinIO, Ceph):
- Bucket reachable —
OKon 200,BADon 404 (missing) / 403 (denied). - Sentinel object (optional
object) —BADif missing,WARNif older thanmax_age_warn_seconds(a stale drop/backup), elseOK.
Requests are signed with AWS Signature V4 written by hand (zero deps, no AWS
SDK); credentials come from access_key_env/secret_key_env (env only), or it
falls back to anonymous for public buckets. path_style: true for MinIO/Ceph.
Tested against an in-test fake S3 (httptest).
smtp — SMTP relays
Verifies an SMTP relay is healthy without ever sending mail:
- Connection & greeting —
ERRORif it can’t connect,BADon a non-220greeting or a greeting missing the optionalexpect_bannersubstring. - EHLO —
BADifEHLOis rejected. - STARTTLS (
starttls: true) —BADif not advertised or the upgrade fails. - Implicit TLS (
tls: true, e.g. port 465) — wraps the connection at once. - Relay certificate — when TLS is negotiated, reads the leaf and reports
WARNunderwarn_days,BADundercrit_days(or expired). - Latency —
WARNovermax_latency_ms.
Default port is 25, or 465 when tls: true. Zero-dep (stdlib net/crypto/tls);
tested against in-test fake relays (plain, STARTTLS, implicit TLS).
smtp:
warn_days: 30
crit_days: 7
targets:
- {name: relay, address: mail.example.com:25, starttls: true}
- {name: smtps, address: mail.example.com:465, tls: true}
elasticsearch — Elasticsearch / OpenSearch
Checks an Elasticsearch or OpenSearch cluster over its HTTP API (any node answers cluster-wide):
- Cluster health (
/_cluster/health) —green→OK,yellow→WARN,red→BAD; the message carries node count, unassigned shards and active shard percentage. - Expected nodes (
expect_nodes) —BADif the cluster reports fewer nodes than expected (a shrunk cluster, even when still green). - Disk watermark (
/_cat/allocation) — per node,WARNoverdisk_warn_pct(default 85, ES low watermark),BADoverdisk_crit_pct(default 90, high watermark). ERRORif the API is unreachable.
Credentials come from env — username + password_env (basic auth) or
api_key_env (API key) — never inline; insecure_skip_verify for self-signed
clusters. Zero-dep (HTTP/JSON); tested against an in-test fake cluster.
elasticsearch:
disk_warn_pct: 85
disk_crit_pct: 90
targets:
- {name: logs, url: https://es.example.com:9200, username: elastic, password_env: ES_PASSWORD, expect_nodes: 3}
mongodb — MongoDB
Read-only health check for MongoDB via the official driver (any member answers cluster-wide):
- Replica-set status (
replSetGetStatus) —BADif there is no healthyPRIMARY,BADfor any member with health 0 (unreachable), and per-secondary replication lag:WARNoverlag_warn_seconds,BADoverlag_crit_seconds. A standalone node (not a replica set) is reported as reachable and the replica-set checks are skipped. - Connections (
serverStatus) —WARNwhen in-use connections exceedconn_warn_pctof the total available. ERRORif the deployment is unreachable or the status query fails.
Credentials come from env (username + password_env, never in the URI/config);
auth_source defaults to admin. Uses go.mongodb.org/mongo-driver/v2 (a
motivated exception to the zero-dep rule, like pgx for Postgres). The finding
logic is unit-tested with a fake collector — no real database in tests.
mongodb:
lag_warn_seconds: 10
lag_crit_seconds: 60
targets:
- {name: rs0, uri: "mongodb://m1:27017,m2:27017/?replicaSet=rs0", username: monitor, password_env: MONGO_PASSWORD}
mysql — MySQL / MariaDB
Read-only health check via the standard go-sql-driver/mysql (a motivated
exception to the zero-dep rule, like pgx for Postgres):
- Reachable & role —
OKwith the server version; reports whether the server is read-only. - Connections —
WARNwhenThreads_connectedexceedsconn_warn_pctofmax_connections. - Replication (only on a replica) —
BADif the IO or SQL thread is not running or the replica is not replicating (Seconds_Behindis NULL); otherwise lagWARN/BADoverlag_warn_seconds/lag_crit_seconds. Works with both the modernSHOW REPLICA STATUSand the legacySHOW SLAVE STATUScolumn names. ERRORif the server is unreachable or a query fails.
Put the password in the DSN via ${ENV} interpolation — it is resolved from the
environment at config load, never stored inline. The finding logic is unit-tested
with a fake collector; no real database in tests.
mysql:
lag_warn_seconds: 10
lag_crit_seconds: 60
targets:
- {name: primary, dsn: "monitor:${MYSQL_PASSWORD}@tcp(db-01:3306)/"}
etcd — etcd
Checks an etcd v3 cluster over its HTTP JSON gateway — no clientv3
dependency:
- Health (
/health) —ERRORif unreachable,BADif the endpoint reports unhealthy. - Leader (
/v3/maintenance/status) —BADif there is no leader (the cluster has lost quorum); otherwise reports the etcd version. - Members (
/v3/cluster/member/list) —BADif fewer members thanexpect_members(quorum risk), else the count is shown in the healthy message.
Optional token auth (username + password_env, resolved from the environment)
and insecure_skip_verify for self-signed clusters. Zero-dep (HTTP/JSON); tested
against an in-test fake gateway.
etcd:
expect_members: 3
targets:
- {name: etcd-01, url: https://etcd-01:2379, insecure_skip_verify: true}
clickhouse — ClickHouse
Checks a ClickHouse server over its HTTP interface (zero-dep):
- Reachability —
/pingmust returnOk.(ERRORif unreachable,BADotherwise) andSELECT version()must answer (ERRORon failure); the healthy message carries the server version. - Replicated tables (
system.replicas) — per table,BADif the replica is read-only (usually a lost ZooKeeper/Keeper session), else replication delayWARN/BADoverdelay_warn_seconds/delay_crit_seconds. Healthy tables produce no finding; a server with no replicated tables adds nothing.
Credentials come from env (username + password_env, sent as HTTP basic auth),
never inline; insecure_skip_verify for self-signed HTTPS. Tested against an
in-test fake HTTP server.
clickhouse:
delay_warn_seconds: 30
delay_crit_seconds: 300
targets:
- {name: ch-01, url: http://ch-01:8123, username: monitor, password_env: CLICKHOUSE_PASSWORD}
vault — HashiCorp Vault
Checks a Vault node over its HTTP API (zero-dep):
- Seal status (
/v1/sys/seal-status) —ERRORif unreachable,BADif the node is sealed (with unseal progressn/threshold) or not initialized. - Role (
/v1/sys/health) — reportsactiveorstandby(bothOK— standby is normal in an HA cluster) with the Vault version.
Both endpoints are unauthenticated; an optional token_env sends X-Vault-Token
for setups that restrict them. insecure_skip_verify for self-signed HTTPS.
Tested against an in-test fake Vault.
vault:
targets:
- {name: vault-01, url: https://vault-01:8200}
memcached — memcached
Checks memcached over its text protocol (zero-dep):
- Reachability — connects and runs
STATS;ERRORif it can’t. - Memory —
WARNwhenbytesexceedsmem_warn_pctoflimit_maxbytes(default 90); otherwiseOKwith version, memory percentage and connection count. The percentage is also the target’s numeric metric (%). - Evictions — a
[evictions]finding carrying the counter as a metric. memcached only exposes a total since startup, not a rate, so there is no useful default threshold: the count is published so the history can chart it (a rising line is the real signal), and it onlyWARNs when you set an explicitevictions_warn. Omitted when the server doesn’t report the stat.
Targets are host[:port] (default port 11211). Tested against an in-test fake
memcached.
memcached:
mem_warn_pct: 90
evictions_warn: 0 # 0 = report only, no threshold
targets: [cache-01, cache-02:11212]
cassandra — Cassandra / ScyllaDB
Reachability check that speaks the CQL native protocol handshake directly — no driver, no authentication:
- Connects and performs
OPTIONS→SUPPORTED(negotiating the CQL version), thenSTARTUP. AREADYorAUTHENTICATEreply means the node accepts CQL connections →OK(with(auth required)noted when authentication is on). WARNwhen the handshake is slower thanmax_latency_ms. Handshake latency is also the node’s numeric metric (ms).BADon a protocolERRORreply,ERRORif the node is unreachable.-
Cluster state — a
clusterfinding rolls the nodes up: how many accept CQL out of those configured, as a metric (nodes).BADbelowexpect_nodes(0 = expect them all),WARNwhen the expectation is met but a configured node is still down. A slow node (WARN) still counts as up — it completed the handshake. Skipped for a single target with noexpect_nodes, where it would only repeat the node’s own finding.This is derived from checkfleet’s own probes, not from
system.peers: reading the cluster’s view of its membership needs aQUERYon an authenticated session, and this module speaks the handshake only. The trade-off is deliberate — it says nothing about nodes absent from your config, but it keeps working on clusters with authentication enabled, with no driver and no credentials. Same shape asetcd’sexpect_members.
Targets are host[:port] (default CQL port 9042). Zero-dep; tested against an
in-test fake CQL server.
cassandra:
expect_nodes: 3 # 0 = all configured nodes must accept CQL
targets:
- {name: cass-01, address: cass-01:9042, max_latency_ms: 500}
mediamtx — mediamtx streaming server
Reads the v3 control API of a mediamtx server: which paths exist, whether each is ready, and what is flowing through it.
This is the layer nothing else in checkfleet can see. ingest says a streamer
can connect and stream says a manifest is being served — but between them
sits the failure that takes a broadcast off the air quietly: the encoder died,
the path went not-ready, and the manifest is still cached downstream, so every
other probe stays green.
- API reachable — one finding per target, with the path count as its metric.
WARNovermax_latency_ms,ERRORwhen the API is unreachable, rejects the credentials (401) or answers anything but 200.ERROR, notBAD: an API that cannot be read says nothing about whether the streams are healthy. - Per path —
BADwhen a path is not ready (no publisher: the encoder is gone),BADwhen it is ready but has received zero bytes (the publisher connected and went silent, which looks identical to health from the outside), otherwiseOKwith the source type, the tracks and the reader count as its metric. expect_pathsturns the question from “are the paths that exist healthy” into “are the paths that must be on the air on the air”. A path that vanished from the server’s config is invisible to the first question andBADunder the second. Without it, every path the server reports is judged.warn_no_readersWARNs on a ready path nobody is reading. Off by default: a stream with no viewers at 04:00 is the middle of the night, not a fault.
Credentials are HTTP basic auth with the password from an env var, and the check
never calls anything but /v3/paths/list — it cannot create or delete a path.
Pagination is followed, because a truncated path list is the same failure as not
checking the missing ones. Zero-dep HTTP/JSON, tested against a fake mediamtx
in-test.
mediamtx:
targets:
- name: mtx-01
url: http://mtx-01:9997 # API base, no path
expect_paths: [live/stream1, live/stream2]
warn_no_readers: false
max_latency_ms: 1000
flow — multi-step HTTP flows
Half of what an operator cares about is not a single request. “Login works” is get a token, use it, check the answer — and a probe that only asks the first of those reports a healthy service while nobody can log in.
A flow is an ordered list of steps. Each step is one HTTP request; a value captured from its response can be substituted into any later step:
extractcapturesname: selector. Three selectors, no more:json:<dotted.path>(a numeric segment indexes an array, soitems.0.idworks),header:<Name>,regex:<pattern with one capture group>.- `` substitutes a captured value into a later step’s
url,headersorbody. That is the entire syntax: no expressions, no filters, no property access. expect_status(default 200),expect_body(a substring) andmax_latency_msper step;max_latency_mson the flow budgets the whole thing, because a login that takes eight seconds is broken regardless of which hop spent them.keep_cookiescarries a cookie jar across the steps, which is what a session login needs.insecure_skip_verifyskips TLS verification.- Credentials come from the environment by name:
headers_envsets a header from a variable,body_envreads the whole request body from one.
The finding names the step that failed. step 2/3 "use the token": expected
status 200, got 401 — “the flow is broken” without saying where is a finding
that saves nobody any time. The first failure stops the flow, since every later
step would fail for the same reason and bury the one that matters.
BAD when the target answered and answered wrong (a status, a missing
substring, a selector that found nothing); ERROR when the check could not
measure at all (unreachable host, a named env var that is unset). One finding per
flow, named after it.
Two rules this module is built around
Nothing in the config is ever executed. The steps are declarative and the substitution resolves one form of reference against values the flow itself captured. A check that evaluated code from its own config would be a new security surface on a process that already holds the credentials of up to 30 production systems — so there is no scripting here, and there will not be.
Captured values are never printed. A login flow captures a token, and a finding travels to a terminal, a CI log and a JSON file. Messages name what was looked for — the selector, the header, the missing capture — never what was found. A URL that had a captured value substituted into it is redacted out of transport errors, which quote the whole URL back.
flow:
flows:
- name: login
max_latency_ms: 5000
steps:
- name: get a token
method: POST
url: https://auth.example.com/token
headers: {Content-Type: application/x-www-form-urlencoded}
body_env: CF_FLOW_LOGIN_BODY # carries the client secret
extract:
token: json:access_token
- name: use the token
url: https://api.example.com/me
headers:
Authorization: "Bearer "
expect_body: '"active":true'
Target discovery
The certs, nats, haproxy, patroni, consul, redis and tls modules can
take their targets from three sources, usable together. Results are merged and
de-duplicated by address; when two sources name the same address the first one
wins, so a hand-written inventory keeps its host name.
checkfleet targets resolves them before any check runs and tags each
discovered host with its source, so you can see what a run would cover without
running it. --no-discover lists only the targets written in the config.
ansible_inventory
A standard Ansible INI inventory (a file or a directory of files):
- host lines and their
ansible_host=value are used; :varsand:childrensections are ignored;- hosts are de-duplicated.
Every discovered host becomes a target on the module’s port (443 for certs,
8222 for nats, 8404 for haproxy, 8008 for patroni, 8500 for consul,
6379 for redis, 443 for tls).
consul_service
Pulls the instances of a service from a Consul catalog — service (required),
address (default 127.0.0.1:8500), scheme, tag, token_env. Only instances
passing their own health checks by default (only_healthy), because a target list
carrying known-dead nodes turns one outage into a second, noisier one.
dns_srv
A list of SRV lookups: name (the full record, e.g.
_nats._tcp.service.consul) and an optional resolver — names served only by
Consul’s DNS on :8600 or a cluster’s CoreDNS are invisible to the system
resolver, so this is not an edge case.
Both sources know a port and keep it by default; set keep_port: false when the
module supplies its own, as certs does with 443.
A source that cannot be reached is a warning in targets and an ERROR finding
in a run — never silence. The hosts the other sources did resolve are still
checked: one broken source must not quietly mean “nothing to check”, which would
report a healthy fleet from a typo in a path.
See Configuration → Target discovery for the full key reference.