Install via Docker / Podman

# Plain docker run
docker run -d \
    --name uptime-kuma \
    --restart unless-stopped \
    -p 127.0.0.1:3001:3001 \
    -v uptime-kuma:/app/data \
    louislam/uptime-kuma:1

Or compose / Quadlet for proper lifecycle management:

# docker-compose.yml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - ./data:/app/data
      - /var/run/docker.sock:/var/run/docker.sock:ro  # only if monitoring docker containers

First run, browse to http://<host>:3001/ — the wizard creates an admin account. The SQLite database lives in /app/data/kuma.db.

Reverse proxy

# Caddy
status.example.com {
    reverse_proxy 127.0.0.1:3001
}

Uptime Kuma uses WebSockets; Caddy supports them by default, nginx needs the usual Upgrade/Connection headers and a long proxy_read_timeout.

The first monitor

Add New Monitor → pick a type:

  • HTTP(s) — URL + expected status code(s) + optional keyword-in-body check + optional certificate-expiry alert.
  • HTTP(s) with JSON Query — for "the API returned {"status": "ok"}"; uses JSONPath to extract and compare.
  • TCP port — for non-HTTP services (Postgres, Redis).
  • Ping — ICMP, but note Docker needs --cap-add=NET_RAW for ping monitors.
  • DNS — resolve a name against a specific server, expect a record.
  • Docker container — check that a container is running (via the mounted Docker socket).
  • Push — Uptime Kuma generates a URL, your service hits it on a heartbeat. Perfect for cron jobs — "if I haven't heard a heartbeat in 24 hours, the backup didn't run." Same idea as healthchecks.io, self-hosted.
  • Database (Postgres / MySQL / MongoDB / Redis / SQL Server) — check a connection succeeds and a query returns expected data.
  • Real-Browser — run a headless Chrome against the page, useful for SPA endpoints where HTTP 200 doesn't mean the page actually rendered.

Useful settings per monitor

  • Heartbeat interval — how often to check. 60s is the default; below 20s for critical, 5-10 min for non-critical so you don't pay rate-limit cost.
  • Retries before "Down" — 2–3 is sane; 0 means a single transient blip pages you.
  • Heartbeat retry interval — once a check fails, retry sooner (e.g. 20s) until either it recovers or "Down" is confirmed.
  • Resend Notification — repeat the notification every N minutes while down; useful for catching alerts you missed.
  • Tags — group monitors by service / project / customer. The dashboard filter and the status pages both use these.

Notifications

Set up notifications once under Settings → Notifications, then attach them to monitors. The channel list is exhaustive; the most-used in practice:

  • Slack / Discord / Teams — webhook URL, channel name. Default templates are good.
  • Telegram — create a bot via @BotFather, paste the token, chat ID.
  • ntfy — self-hosted push notifications (ntfy.sh or a local instance); maps to a topic the mobile app subscribes to.
  • SMTP email — works fine with Stalwart, Mailgun, etc.
  • Generic webhook — POST the alert JSON anywhere. Useful for tying into n8n (see that tutorial) for "alert → create ticket in Linear" flows.

Status pages

Status Pages → Add New Status Page. Pick a slug (becomes https://status.example.com/status/<slug>), pick which monitors / monitor groups to show, customize the title and colour. Public viewers see uptime history, current status, and ongoing incidents; no auth required.

For incident transparency, the "Incident" banner at the top of a status page is a manual message that operators post during an outage (with status: investigating / identified / monitoring / resolved). Status-page status auto-updates from the monitor results below the banner.

Per-monitor SSL cert expiry

HTTP(s) monitors automatically track certificate expiry of the target. Default alert is 21/14/7 days before expiration. For sites behind Let's Encrypt + ACME, this is a useful canary against "auto-renewal silently broke a month ago."

Multi-instance / multi-region

Uptime Kuma 2.x (currently in beta) introduces remote browser instances and remote regions — checks running from a different IP than the central one. For 1.x: deploy multiple Kumas in different geographic locations and have them monitor each other plus your services. The "Uptime Kuma is the one watching the watchers" problem is real; run two instances in two clouds for the cheapest workable HA.

Backups

The entire state lives in the data volume: kuma.db (monitors, alerts, history), uploaded images, status-page config. A nightly file-level backup of that directory (restic, rsnapshot, ZFS send) is enough to rebuild a fresh Kuma instance.

API and Prometheus metrics

  • Uptime Kuma exposes a Prometheus-compatible /metrics endpoint (with the right API key header), so the same data flows into Grafana dashboards via Prometheus (see that tutorial).
  • The REST API is undocumented but stable enough; Status Page JSON is at /api/status-page/<slug> for embedding.