Skip to content

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

compose.yaml (monitoring stack, excerpt)
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: /ping on Sonarr/Radarr, /api/server/ping on Immich, /api/v1/status on Seerr, /v1/health on ntfy, /healthz on code-server.
  • Point monitors at internal addresses, not public names behind SSO. A public hostname behind an SSO proxy returns 200 for 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:

/etc/borgmatic.d/<host>.yaml (excerpt)
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.


Sources & further reading