Uptime Kuma
A homelab has a specific failure mode: something breaks quietly and you find out three days later when you go to use it. Uptime Kuma is the fix. It checks everything on a schedule and tells you the moment something stops answering.
It is the first thing I would install on a new lab after Docker itself.
Uptime Kuma is a self-hosted monitoring tool — a Pingdom or UptimeRobot you run yourself, with no check limits.
- Many monitor types — HTTP, TCP, ping, DNS, Docker container, database queries, push
- 90+ notification providers via a built-in integration list: ntfy, Discord, Telegram, email, Gotify, webhooks
- Status pages, public or private, with your own domain
- Certificate expiry monitoring, with warnings before it bites
- Maintenance windows, so planned downtime does not page you
- Genuinely good-looking, which matters for a thing you glance at daily
1. The Compose file
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
volumes:
- uptime-kuma:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 3001:3001
volumes:
uptime-kuma:
Notes:
:1, not:latest. Version 2 is in beta and changes the database backend. Pin the major version and move deliberately.- The Docker socket is mounted read-only and is optional — it enables the "Docker Container" monitor type, which checks whether a container is actually running rather than whether a port answers. Drop the line if you would rather not mount the socket at all; everything else still works.
- Port 3001 is unusual enough to rarely conflict.
2. Create the admin account
Browse to http://<host-ip>:3001. The first screen sets up the admin user. There is no default password to change, which is a nice touch.
3. Add your first monitor
Add New Monitor:
| Field | Value |
|---|---|
| Monitor Type | HTTP(s) |
| Friendly Name | Seerr |
| URL | http://192.168.1.50:5055 |
| Heartbeat Interval | 60 seconds |
| Retries | 3 |
| Accepted Status Codes | 200-299 |
Retries matters more than it looks. With retries at 0, a single slow response at 3am sends you an alert. At 3, a service has to fail three consecutive checks before it counts as down — which filters out nearly all the noise without meaningfully delaying real alerts.
4. Pick the right monitor type
The default HTTP check is not always what you want:
| Type | Checks | Good for |
|---|---|---|
| HTTP(s) | An HTTP response | Web apps |
| TCP Port | Port accepts a connection | Databases, SSH, game servers |
| Ping | ICMP reply | Switches, APs, printers |
| DNS | A record resolves | Your AdGuard instance |
| Docker Container | Container is running | Things with no network listener |
| HTTP(s) - Keyword | Response contains specific text | Apps that return 200 while broken |
| Push | Your script calls Kuma | Backups, cron jobs |
HTTP - Keyword is the underrated one. Plenty of apps return a cheerful 200 OK on a page that says "database connection failed". Checking for a keyword you expect to see on a working page catches that; a status code check does not.
Push is the other one worth knowing. Kuma gives you a URL, and your script calls it on success:
#!/bin/bash
restic backup /mnt/user/data && curl -fsS "http://192.168.1.20:3001/api/push/AbC123?status=up&msg=OK"
If the backup fails, the URL is never called, and Kuma alerts because the push did not arrive. This turns "did my backup run?" from a thing you hope about into a thing you get told about.
5. Set up notifications
Settings → Notifications → Setup Notification
ntfy is the easiest self-hosted option — no account, no webhook registration, works on iOS and Android:
| Field | Value |
|---|---|
| Notification Type | ntfy |
| Server URL | https://ntfy.example.com |
| Topic | homelab |
| Priority | 5 for down, so it breaks through Do Not Disturb |
Tick Default enabled and Apply on all existing monitors, or you will add a monitor next month, forget the notification, and learn nothing when it fails.
Hit Test. An untested notification channel is not a notification channel.
6. Monitor certificate expiry
On any HTTPS monitor, Certificate Expiry Notification warns you before a certificate lapses.
If your certificates come from Let's Encrypt via Traefik or NPM they should auto-renew — but "should" is exactly the assumption this catches. Renewal breaks silently more often than you would expect, usually because a DNS API token expired.
7. Build a status page
Status Pages → New Status Page.
Add monitors, group them, set a custom domain if you want. Two genuine uses:
- Public, for the people you share media with, so they can check before messaging you
- Private, as a homelab dashboard that loads faster than the main UI
You can pin it to /status and give it your own logo and description.
8. What to actually monitor
Resist monitoring everything. A dashboard with forty red squares gets ignored. Monitor what you would want to be woken for, and layer it:
Infrastructure first — these failing explains everything else:
- Proxmox hosts, by ping
- The Unraid box, by ping
- Your router
- AdGuard, by DNS query type — a dead DNS server takes the whole house down and is the single highest-value check on this list
Then the services people notice:
- Plex and Jellyfin
- Seerr
- Home Assistant
Then the plumbing:
- Traefik and NPM
- The *arr stack, by HTTP
- Gitea
Then the jobs, via push monitors: backups, snapshot syncs, anything on a cron.
Do not run Uptime Kuma on the host it is monitoring
If Kuma lives on the same machine as everything it watches, a host failure takes down both the services and the thing that would have told you. Put it on a different node — ideally the one that does the least.
Updating
Staying on the :1 tag keeps you on version 1.x. Moving to 2.x is a deliberate migration, not a pull.
Backup
docker compose stop
docker run --rm \
-v uptime-kuma_uptime-kuma:/data \
-v $(pwd):/backup \
alpine tar czf /backup/uptime-kuma-$(date +%F).tar.gz -C /data .
docker compose start
Stop it first — SQLite again. There is also Settings → Backup in the UI, though upstream considers it deprecated in favour of copying the data directory.
Troubleshooting
Monitor shows down but the service works in a browser. Kuma is checking from inside a container. docker exec uptime-kuma curl -I <url>. Usually DNS, or a hostname that only resolves on your workstation.
Certificate errors on internal HTTPS services. Self-signed certificates fail validation. Turn off Ignore TLS/SSL error — that is, turn the ignore on — for that monitor.
Notifications not arriving. Test the notification directly from Settings. If the test works but real alerts do not, the notification is not attached to the monitor.
Alert storms at the same time every night. Something is restarting — a backup job, a log rotation, a scheduled Docker restart. Use a maintenance window rather than lowering the check frequency.
Docker container monitors all show down. The socket mount is missing, or permissions block it. docker exec uptime-kuma ls -l /var/run/docker.sock.
High CPU with many monitors. Intervals are too aggressive. 60 seconds is fine for most things; infrastructure can sit at 20, and a status page for friends does not need 10.
Uptime percentages look wrong after a restart. Kuma counts the time it was down as unknown, not up. It settles.
Where this sits in my lab
Uptime Kuma runs on proxmox2, deliberately away from the Unraid box and the first Proxmox node, so it survives to report their failures.
It watches both hypervisors, the Unraid array, AdGuard by DNS query, and about twenty services by HTTP — plus push monitors for the nightly backups. Notifications go to ntfy at priority 5, which is loud enough to matter and rare enough that when the phone buzzes I actually look.
The AdGuard DNS check has earned its keep more than anything else here. When DNS dies, everything in the house appears broken simultaneously and the cause is not obvious from the symptoms.