Kuma Stops Being a Mystery Background Process
Why: Uptime Kuma had been running for months with five monitors, one of them pointed at a service retired in June, and Damien could not have said what it watched or recalled ever hearing from it. It was ~15% of a capable tool sitting idle while we were about to hand-write the alert manager it already is.
This is the first implementation step of the monitoring redesign. The governing idea, arrived at after two adversarial design reviews: Kuma is not the product — it is the memory. The intelligence stays in small purpose-built scripts, which is where it already lives on this box and where it demonstrably works. What those scripts have always lacked is state: they fire once into Telegram and forget, so nothing clears, nothing repeats, and nothing notices when a watcher itself dies. Kuma supplies exactly that and nothing else.
1. Kuma was never broken — it was unconfigured
Before changing anything we checked why it had been silent. Its Telegram notification was already configured, already default, already pointing at the same chat every other alert on this box uses, and all five monitors were wired to it. The heartbeat history explains the silence entirely:
monitor up beats down beats
Plex 2798 7
Nginx 2794 3
Music Discovery 2798 7
Plex (Remote) 2800 8
It monitors four things that essentially never fail. It had nothing to say. The problem was coverage, not plumbing — which meant new monitors would inherit working notifications for free.
2. What was built
/home/plex/ops/provision-kuma.py— idempotent monitor provisioning over Kuma's socket.io API,--dry-runby default. Desired state lives in code, so what is monitored and why is reviewable and re-runnable instead of buried in a web UI./home/plex/bin/dns-apex-check.sh(cron*/15) — asserts each apex resolves, via an external resolver, to the same address the router's DDNS advertises./home/plex/bin/lib-kuma.sh— sharedkuma_pushhelper.- Six push monitors — three apex-correctness, three dead-man's switches on the existing backup jobs.
- Success/failure reporting added to
backup-plex1.sh,backup-plex3.sh,arr-config-backup.sh.
3. Why push monitors rather than Kuma's native checks
Kuma natively answers "is X reachable?". Almost none of this server's real failures are reachability failures. room101.com resolved perfectly for 16 days — to the wrong address. The nightly plex1 backup runs, reports success, and quietly fails to converge. Media requests stall while every component in the pipeline reports healthy. Those need local knowledge, so the check stays in a script and Kuma receives only the verdict.
The apex check is the clearest case. It must query an external resolver: this box's /etc/hosts split-horizon maps every domain to the LAN IP, so anything resolving locally looks perfect while the public internet gets a dead address. That is precisely how the July outage hid. Had this check existed on 2026-07-19, it would have caught the miss within 15 minutes instead of 16 days.
4. Tuned against notification fatigue, deliberately
Both design reviews independently warned that the real failure mode of a monitoring system is the operator muting it. Kuma has no geometric backoff — only a flat "resend every N minutes" — so resendInterval is set to 0: notify once, do not nag. The backup monitors use a 24h expected heartbeat with a 27h grace, because a backup running long is not yet a backup that failed. Kuma's maintenance windows are the snooze mechanism for a known-open issue awaiting a part (the dead plex2 backup drive being the obvious case).
⚠ Correction (2026-08-05): the grace periods described in this section did not exist. They were set via retryInterval with maxretries=0, and Kuma consults retryInterval only while retrying — with no retries, the first missed heartbeat is an instant DOWN plus a notification. Combined with the pipeline monitors' interval being set equal to their pusher's cron period, this made flapping certain and produced 11 false alerts in a single night. Fixed in 2026-08-05 alert-noise-and-remediation §2, which also explains the correct model.
5. Two gotchas worth recording
Kuma 2.x breaks the client library. uptime-kuma-api 1.2.1 targets Kuma 1.x, and Kuma 2.3.2 added monitor.conditions as a NOT NULL column the library never sends. Every add_monitor died with SQLITE_CONSTRAINT: NOT NULL constraint failed: monitor.conditions. Existing rows store '[]', so a one-function shim injecting conditions: [] fixes it. This is exactly the socket.io version-coupling fragility raised in review — and also the argument for keeping that coupling in a provisioning-time script: it broke loudly, at the one moment someone was watching, rather than silently rotting as a runtime dependency.
Push tokens cannot be preset by the client library — Kuma generates them. So the provisioner reads them back after creation and writes /home/plex/ops/push-tokens.env (mode 600), which the checker scripts source. Tokens therefore live in exactly one place: a monitor deleted and recreated in the UI mints a new token, and the fix is re-running the provisioner rather than hand-editing shell scripts. (Reading back immediately also needs a short retry — the client caches monitors from socket events and ones created seconds earlier may not have landed.)
6. Monitoring must not be able to cause an outage
kuma_push returns 0 on every failure path — missing token file, missing token, Kuma down, network stall — and its curl is bounded at 5s connect / 10s total. A monitoring hook that fails a backup job is worse than no monitoring. Nothing is lost by swallowing these errors, because the dead-man's-switch on the Kuma side is what catches a job that stops reporting. Verified explicitly: bogus token → exit 0, missing token file → exit 0.
The arr-config-backup.sh abort path (exit 1 when /media/plex3_backup is unmounted) deliberately does not ping. No ping is the correct signal there, and the grace period converts that silence into an alert on its own.
7. Verification performed
- Checker run live: all three apexes correctly matched
cw526dc.glddns.com→97.113.247.79. - Kuma recorded the heartbeats with readable messages (
skyhouse.dev -> 97.113.247.79). - All six monitors confirmed wired to SkyHouse Server Downtime Alert in
monitor_notification. - A forced DOWN then recovery on the plex3 job monitor produced both transitions flagged
important=1, and both Telegram messages were confirmed received by Damien — so the full chain (script → push → Kuma state change → Telegram) is proven end to end, including that alerts clear themselves. - All five edited/created scripts pass
bash -n; the provisioner compiles and is idempotent (second run: "0 created, 6 already present").
Worth knowing: Kuma sends via its own internal Telegram client, not through our hardened /usr/local/bin/telegram_notify.sh, and it does not log notification dispatch at default verbosity. So Kuma-originated alerts will not appear in /var/log/telegram_notify.log — there are now two independent delivery paths to the same chat, and only the script-originated one is audited.
Six monitors where there were five, but for the first time they watch things that actually break — and a job that stops reporting is now itself an alert.
← Back to Admin Hub