Uptime Kuma¶
Uptime Kuma answers "is it up?" for every
service, and, through push monitors, "did last night's job actually run?". Every
DOWN/UP goes to the ntfy alerts topic.
It runs in the same stack as the Beszel hub and the Dozzle hub on
control-host.
Compose¶
services:
uptime-kuma:
image: louislam/uptime-kuma:2.5.6
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- /mnt/appdata/uptime-kuma:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro # for "Docker container" monitors
Kuma keeps everything (monitors, history, notification settings) in a SQLite database
under /app/data, so that directory goes into the nightly backup. No .env is needed:
credentials are set in the UI on first start.
Notification: ntfy¶
One ntfy notification, set as default and applied to all existing monitors, so new monitors get it automatically:
| Setting | Value |
|---|---|
| Server URL | http://<YOUR_SERVER_IP>:88 (LAN, so it still works when the tunnel or DNS is down) |
| Topic | alerts |
| Auth | access token of the write-only kuma ntfy user |
| Priority | 5 for DOWN, 3 for UP |
Use the LAN address, not the public name. The first version posted through the public URL, which meant "the tunnel is down" was exactly the alert that couldn't be delivered.
Monitors¶
Guidelines that came out of cleaning up a monitor list that had drifted:
- Check a real health endpoint with a keyword or status, not just "port open".
Most apps have one:
/pingon Sonarr/Radarr,/api/server/pingon Immich,/api/v1/statuson Seerr,/v1/healthon ntfy,/healthzon code-server. - Point monitors at internal addresses, not public names behind SSO. A public
hostname behind an SSO proxy returns
200for the login page, so the monitor stays green while the backend is dead. - Keep one deliberate end-to-end check of the remote-access path: an HTTP keyword
monitor on a public health URL that goes through DNS → tunnel → reverse proxy →
service and expects a body such as
"healthy":true. That catches tunnel or proxy failures that internal checks can't see. - DNS monitors against the firewall's resolver, plus HTTP/port checks on the firewall GUI, cover the network edge.
- Pause, don't delete, monitors for services you've retired. History stays, and they stop generating noise.
- Dead monitors are worse than none. A few monitors still aimed at a retired host produced thousands of failures in three days, which trains you to ignore the alert stream.
Groups¶
| Group | Contains | Why grouped |
|---|---|---|
| Media Server | Plex, Sonarr, Radarr, qBittorrent, Seerr, Immich … on the NAS | One maintenance window covers all of them during the nightly parity sync |
| Backups | every push monitor for borgmatic and SnapRAID | One place to see "did every job report?", and a daily summary reads this group |
Children inherit a group's maintenance, so a new media monitor is covered as soon as it's in the group.
Push monitors as dead-man switches¶
A normal monitor can't tell you a nightly job didn't run. Nothing is down, the job just never happened. (That's how a SnapRAID sync once stayed stuck for a month without an alert.) A push monitor inverts it: the job calls a URL when it finishes, and Kuma goes DOWN if no call arrives in time.
| Setting | Value |
|---|---|
| Monitor type | Push |
| Heartbeat interval | 90000 s (25 h): a nightly job plus an hour of slack |
| Group | Backups |
The job reports up on success and down (with a message) on a known failure, so you get
an alert straight away for a reported failure, and after 25 h for a job that never ran.
One monitor per job and per destination. Each server's borgmatic has a LAN monitor
and an offsite monitor (borgmatic server1 lan, borgmatic server1 offsite). If they
shared one, a good offsite run would turn it green again and hide a failed LAN run.
borgmatic¶
borgmatic has a native hook. The push URL contains a token, so keep it in a root-only file on the host instead of in the config:
uptime_kuma:
push_url: !include /etc/borgmatic/uptime-kuma-url # root:root 0600
borgmatic pushes up on finish and down on fail. See Borgmatic.
SnapRAID (or any script)¶
Wrap the job, decide whether it was clean, then push:
url=$(cat /etc/<YOUR_HOSTNAME>/kuma-push-snapraid 2>/dev/null) # root 0600
[ -n "$url" ] && curl -fsS -m 15 -G -o /dev/null \
--data-urlencode "status=$status" --data-urlencode "msg=$msg" "$url"
A run counts as clean only when the script exits 0, logs no WARN/ERROR line and
logs its completion marker. A sync that exits 0 because it refused to run (deleted-file
threshold) is not a success. See SnapRAID.
The same pattern covers anything periodic: the firewall health poller pushes every 5 minutes to a monitor with a 600 s interval, so a dead poller is noticed too.
A daily backup summary from Kuma's own database
Because every job already reports into the Backups group, a small script can open Kuma's SQLite DB read-only each morning and send one ntfy message listing every job: last push OK and under 25 h, last push failed (with the failure streak), or overdue. No credentials, no SSH, and new jobs show up as soon as their monitor is in the group.
Maintenance windows aligned to jobs¶
The nightly parity sync on the NAS stops or pauses the media containers for about an hour. Without a window that's a burst of DOWN/UP alerts every night.
| Setting | Value |
|---|---|
| Strategy | Cron, starting a few minutes before the job (e.g. 55 1 * * * for a job at 02:00) |
| Duration | longer than the slowest run in the logs (runs took 47–71 min; the window is 100) |
| Timezone | set explicitly, the same zone the host timers use |
| Date range | none (an end date silently turns the window off) |
| Affected | the Media Server group, not individual monitors |
Give the deployment tool's "service down" alerter the same quiet window, or it will page for the same paused containers. A run still going when the window closes alerts normally, which is what you want: that's a real problem.
When a window stops working, see Monitoring & alerts troubleshooting.