Monitoring 📖 11 min read

Uptime Kuma Uptime Monitoring

Uptime Kuma checks your websites and services on a schedule and tells you when they stop answering. It's quick to set up and easy to live with, which makes it a good first monitoring tool. This guide installs it with Docker Compose, explains which monitor type proves what, uses push monitors to catch jobs that quietly stop running, and covers the question every monitoring setup has to answer: who watches the watcher?

Jump to: install with Compose, choosing monitor types, notifications or status pages.

Before you start

Uptime Kuma is a self-hosted uptime monitor. It checks your websites, servers and services on a schedule, records response times, and tells you when something stops answering. It does one job, with a web interface, and it's much quicker to set up than a full metrics stack. That makes it a good first monitoring tool, and a useful second one if you already run Prometheus.

You need a Linux host with Docker Engine and the Compose plugin; the Docker Installation Guide covers the install. Uptime Kuma is light. A small VM or a Raspberry Pi 4 handles a few hundred monitors.

Think about where it runs before you install it. A monitor on the same machine as the services it watches goes down with them, so you'd never get the alert that matters most. The best place is a separate, cheap machine: a different VM, a Pi on your network, or a small cloud instance if you're watching services from outside your network. Wherever it runs, have something else watch Uptime Kuma itself. The last section covers that.

Install with Docker Compose

mkdir -p ~/stacks/uptime-kuma && cd ~/stacks/uptime-kuma
compose.yaml
name: uptime-kuma

services:
  uptime-kuma:
    image: louislam/uptime-kuma:2
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - kuma-data:/app/data

volumes:
  kuma-data:

The tag 2 follows the 2.x release series. Version 2.0 added MariaDB support and changed how the internal database is stored, and the first start after moving from 1.x runs a migration that can take a while on a large history. If you're upgrading an existing 1.x install, back up the volume first, then read the 2.0 release notes in the project's releases page.

The port is bound to 127.0.0.1, so only a reverse proxy on the same host can reach it. If you'll open the interface directly from other machines on a trusted LAN, change it to the host's LAN address, such as "192.168.1.20:3001:3001". Don't publish it as plain 3001:3001 on an internet-facing machine: Docker's port publishing bypasses ufw and firewalld, as Docker Networking Explained describes.

docker compose up -d
docker compose logs -f        # wait for "Listening on 3001", then Ctrl+C
curl -sI http://127.0.0.1:3001 | head -n 1

Open the interface. On first visit, Uptime Kuma asks you to create the admin account and, on 2.x, to choose the database. The embedded SQLite option is right for most single-host installs; MariaDB is for very large installs or when you already run a MariaDB server. Use a long, unique password. Then, under Settings → Security, turn on two-factor authentication. This account can see every internal address you monitor.

Putting it behind HTTPS

Uptime Kuma pushes live updates to the browser over a WebSocket, so a reverse proxy must pass the upgrade headers through. Caddy does it automatically:

status.example.net {
    reverse_proxy 127.0.0.1:3001
}

With Nginx, include the upgrade headers in the location block:

location / {
    proxy_pass http://127.0.0.1:3001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Without these headers the dashboard loads but monitors never update, and the browser console shows a failed WebSocket connection. Under Settings → Reverse Proxy, set Trust Proxy to yes so the client addresses in the logs are the real ones. The Nginx Web Server Setup guide covers obtaining a certificate.

Choosing monitor types

Select Add New Monitor and choose a type. Picking the right one decides whether an alert means something.

TypeWhat it provesUse it for
HTTP(s)The URL returned an acceptable status code within the timeoutWebsites and web apps
HTTP(s) - KeywordThe response body contains (or doesn't contain) a given stringApps that return 200 OK with an error page
HTTP(s) - Json QueryA value in a JSON response matches what you expectHealth endpoints that report component status
TCP PortA TCP connection openedDatabases, mail servers, SSH
PingThe host answers ICMP echoRouters, switches, whole machines
DNSA record resolves, optionally to an expected valueYour own domains and resolvers
Docker ContainerA container on a connected Docker host is runningContainers with no network endpoint
PushSomething called a unique URL within the intervalCron jobs, backups and anything that runs on a schedule

The keyword monitor deserves more use than it gets. Many apps return 200 OK with a maintenance page or a database error when they're broken. An HTTP monitor reports those as up. A keyword monitor that looks for text only a working page contains, such as your site's name in the page title, catches them.

Configure a monitor well

A few settings decide whether you trust the alerts or start ignoring them:

  • Heartbeat interval. 60 seconds suits most services. Checking every 20 seconds rarely tells you anything sooner that matters, and it multiplies the load on the monitored service.
  • Retries. Set 2 or 3, with a heartbeat retry interval of 20 to 30 seconds. A single dropped packet then doesn't wake anyone, but a real outage is still reported within about two minutes.
  • Request timeout. Keep it below the interval. A timeout of 48 seconds on a 60-second interval is the default ratio and works well.
  • Accepted status codes. The default is 200-299. If the URL redirects to a login page, either follow redirects (the default) or monitor the final URL directly.
  • Certificate expiry notification. Turn it on for HTTPS monitors. Uptime Kuma then warns you days before a certificate expires, which catches a renewal job that has silently stopped.
  • Upside down mode inverts the result. It's for checking that something is not reachable, such as an admin port that should be closed from outside.

Group related monitors with tags, or with a Group monitor that shows as down when any monitor in it is down. Groups also make maintenance windows easier to apply.

Push monitors for scheduled jobs

Every other monitor type asks "can I reach it?". A push monitor turns that around: the job calls Uptime Kuma, and if no call arrives within the interval, the monitor goes down. That's how you find out a backup stopped running weeks ago.

Create a monitor of type Push and set the interval a little longer than the job's schedule, for example 25 hours for a daily job. Uptime Kuma shows a unique URL. Call it at the end of the job, only when the job succeeded:

#!/usr/bin/env bash
set -euo pipefail

/usr/local/bin/backup-miniflux.sh

curl -fsS -m 10 --retry 3 \
  "https://status.example.net/api/push/AbCdEf1234?status=up&msg=OK" > /dev/null

Replace the URL with the one Uptime Kuma gives you. Because of set -e, a failed backup stops the script before the curl, so no heartbeat is sent and you're alerted when the interval runs out. Keep the push URL private: anyone who has it can report your job as healthy.

Monitoring Docker containers

The Docker Container monitor type reads container state from the Docker API. To use it, Uptime Kuma needs the Docker socket, and access to the Docker socket is equivalent to root on that host, as Hardening Docker Containers explains. Decide whether a running-or-not check is worth that.

Usually an HTTP or TCP monitor on the service the container provides is better anyway: it proves the service answers, not just that the process exists. If you still want container monitors, add the socket read-only to the volumes and then add it under Settings → Docker Hosts as /var/run/docker.sock:

    volumes:
      - kuma-data:/app/data
      - /var/run/docker.sock:/var/run/docker.sock:ro

The :ro stops the container replacing the socket file. It doesn't limit the API calls made through it.

Notifications

Under Settings → Notifications, add at least one channel. Uptime Kuma supports dozens, including email over SMTP, Telegram, Discord, Slack, Matrix, ntfy, Gotify and Pushover. A few principles apply whichever you choose:

  • Send the test notification from the setup dialog before you rely on it. Most broken alerting is a typo in a token or an SMTP port, found during the outage.
  • Use a channel that doesn't depend on what you're monitoring. Email through your own mail server isn't much use for reporting that the mail server is down. A push service on someone else's infrastructure, such as ntfy.sh or Pushover, is independent.
  • Enable it as default for new monitors, so you don't forget to attach it.
  • Set up a second channel for the few monitors that really matter.

Self-hosted push services such as ntfy or Gotify are a good fit, as long as they don't run on the same host as Uptime Kuma.

Maintenance windows

Before planned work, create a maintenance window under Maintenance and attach the affected monitors. During the window, failures don't trigger notifications and the status page shows the work as planned. Windows can be one-off or recurring, for example every Sunday from 03:00 to 03:30 if that's when your hosts reboot for updates. Remember to set the time zone in Settings → General, or the window lands at the wrong hour.

Status pages

A status page shows the state of chosen monitors to people who don't have an account, such as family using your self-hosted services or users of a small project. Create one under Status Pages, add monitors to sections, and publish it.

A status page reveals monitor names, and with them which services you run. Name public monitors for what users see (Website, Photo library), not by internal host name or address, and leave internal infrastructure off public pages. Status pages can be served on their own domain, which is set under the page's settings.

Backups and updates

All monitors, settings and history live in the kuma-data volume. Settings → Backup in older versions exported JSON, but that export is deprecated and doesn't include everything. Back up the volume instead, with the container stopped so the SQLite database is consistent:

cd ~/stacks/uptime-kuma
docker compose stop
docker run --rm -v uptime-kuma_kuma-data:/source:ro -v ~/backups:/backup alpine:3 \
  tar -czf /backup/uptime-kuma-$(date +%F).tar.gz -C /source .
docker compose start

To update within the 2.x series, back up first, then pull and recreate:

docker compose pull
docker compose up -d
docker compose logs -f

The Docker Volumes and Backups guide covers restoring and scheduling these backups.

Who watches the watcher?

If Uptime Kuma stops, it stops sending alerts too, and silence looks exactly like everything being fine. Close the loop with an external check:

  • Point a free external uptime service at your status page or at the Uptime Kuma login page.
  • Or run a second Uptime Kuma instance somewhere else, and have each one monitor the other.
  • If you run Prometheus, scrape the /metrics endpoint Uptime Kuma exposes and alert when the scrape fails. The endpoint needs an API key, which you create under Settings → API Keys.

Troubleshooting

The dashboard loads but monitors stay grey or never update

The WebSocket connection is failing, almost always because the reverse proxy isn't passing the Upgrade and Connection headers. Check the browser's developer console for a failed socket.io request.

A monitor flaps between up and down

Raise the retries to 3 and look at the response-time graph. Times creeping up towards the timeout point at a slow service, not a network problem; raising the timeout hides the symptom, so look at the service.

Ping monitors fail for hosts that are clearly up

The target, or a firewall in between, drops ICMP. Use a TCP monitor on a port the host serves instead.

SQLITE_BUSY or database is locked errors

The volume is on a network filesystem such as NFS or SMB, which SQLite's locking doesn't work well on. Keep the volume on local disk, or switch to MariaDB.

Next steps