Uptime Kuma: Self-Hosted Status Monitoring with Docker Compose
Set up Uptime Kuma v2.5.5 with Docker Compose: HTTP, keyword and push monitors, Telegram alerts, and WebSocket-correct operation behind Traefik.
When a service goes down, in the worst case you hear about it from a customer. UptimeRobot cut its free plan in 2024 to 50 monitors with 5-minute intervals; BetterStack gets noticeably expensive past the fifth monitored service. Uptime Kuma provides the same core features for self-hosted infrastructure with no subscription. If you already run Docker Compose on your VPS, you'll have the stack up in under ten minutes; the downtime cost calculator on this site helps you quickly put a number on what an outage actually costs.
What Uptime Kuma Adds Beyond a Simple Ping Check
An ICMP ping only confirms that a host responds. Uptime Kuma also verifies HTTP status codes, searches the response body for a specific keyword, and warns you before TLS certificates expire. Over 90 notification channels are built in — including Telegram, Slack, email, and generic webhooks.
Supported Monitor Types at a Glance
| Type | What is checked | Typical use case |
|---|---|---|
| HTTP(s) | Status code 2xx/3xx | Website, REST endpoint |
| HTTP(s) Keyword | Status code and body content | CMS, login page |
| TCP Port | Connection establishment | Database, SSH |
| Ping | ICMP echo | Router, bare-metal server |
| DNS Record | Resolved value | A record, DMARC entry |
| Push | Waits for heartbeat request | Cron job, background worker |
| Docker Container | Container status via socket | Your own Compose stacks |
The Push Monitor: Inverting the Logic
Classic polling monitors miss the case where a job simply stops starting — a service that silently ceases to run stays invisible. Push mode reverses the direction: Uptime Kuma waits for a signal that never arrives and fires the alert once the configured interval is exceeded. The push URL looks like /api/push/<token>?status=up&msg=OK&ping= and can be called via curl from any cron job or script.
Setting Up the Stack: the Compose File and the SQLite Problem
The official image is on Docker Hub at louislam/uptime-kuma. The 2 tag always points to the latest stable v2.x release — currently 2.5.5. The latest tag, by contrast, includes unstable development builds; the official Docker Tags wiki explicitly recommends against it for production. This is a deliberate choice: in the Uptime Kuma project, latest is the rolling-development track, not the release track — the opposite of the usual convention.
The Minimal Compose File
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
volumes:
- uptime-kuma:/app/data
ports:
- "3001:3001"
environment:
- TZ=Europe/Berlin
restart: always
volumes:
uptime-kuma:
All application state lives in /app/data inside the container — that's where Kuma stores the SQLite database kuma.db, all configuration, and the heartbeat history.
Why a Named Volume Instead of a Bind Mount
If you map /app/data to a host directory instead, you may encounter the following error on slow storage — SD cards, NFS mounts, certain VPS storage backends:
[ERROR] SQLITE_BUSY: database is locked
The cause: Uptime Kuma opens the database in WAL mode and writes heartbeat data at short intervals. On high-latency media, these writes overlap until SQLite exceeds its built-in timeout. Named Docker volumes live under /var/lib/docker/volumes/ on the local host filesystem and reliably avoid this bottleneck. If you prefer a bind mount for backup reasons, make sure the target directory is owned by UID 1000 before first start:
mkdir -p ./kuma-data
chown 1000:1000 ./kuma-data
Then use ./kuma-data:/app/data in the Compose file.
Setting Up Monitors and Wiring Telegram Notifications
After the first start at http://localhost:3001, Kuma guides you through creating an admin account. The approach mirrors the pattern in Docker Compose: Multi-Service Setup with Healthchecks and a Custom Network: get the stack running first, then wire up monitoring and dependencies.
HTTP Monitor with Keyword Verification
For a simple website, the HTTP(s) type is sufficient. Choose HTTP(s) Keyword when you need to confirm that not only the status code is correct, but the body actually contains the expected content. This is especially useful when your application also returns HTTP 200 in maintenance mode — without keyword checking, the outage would go unnoticed.
Telegram Alerts: Token, Chat ID, and Test
- Open Telegram, search for
@BotFather, and send/newbot. You receive a token in the format123456789:ABCdefGHI.... - Send your new bot a message so that a chat object is created.
- Call
https://api.telegram.org/bot<TOKEN>/getUpdatesand read thechat.idfrom the JSON response. For group chats this value is negative — the minus sign must be included. - In Uptime Kuma: Settings → Notifications → Add → Telegram. Enter the token and chat ID, then click Test.
At idle with 15 active monitors (interval: 60 seconds), memory usage on our VPS is around 65 MB RSS, measured with:
docker stats --no-stream --format "{{.MemUsage}}" uptime-kuma
This makes Uptime Kuma viable even on small 1 GB instances that already host Traefik as a reverse proxy and several other services.
Running Behind Traefik: Getting WebSocket Right
Uptime Kuma's frontend communicates exclusively via Socket.IO, which uses WebSocket under the hood. Without correct proxy configuration, after login you'll see a grey screen with "Cannot connect to the socket server" — the browser loads HTML, but the WebSocket connection fails at the proxy.
Client Traefik Uptime Kuma
| | |
|--- GET / ---> |--- GET / ----> |
|<-- 200 HTML --- |<-- 200 HTML ---- |
| | |
|--- WS Upgrade ---> |--- WS Upgrade ----> |
|<-- 101 Switching --- |<-- 101 Switching ---- |
| | |
|<===== Socket.IO frames ========================> |
Traefik Labels for a Single Instance
Traefik forwards Upgrade and Connection headers by default, so no custom WebSocket middleware is needed. Sticky sessions would only be required when running multiple Uptime Kuma replicas — unnecessary for a single instance.
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
volumes:
- uptime-kuma:/app/data
environment:
- TZ=Europe/Berlin
restart: always
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.uptime-kuma.rule=Host(`status.example.com`)"
- "traefik.http.routers.uptime-kuma.entrypoints=websecure"
- "traefik.http.routers.uptime-kuma.tls.certresolver=letsencrypt"
- "traefik.http.services.uptime-kuma.loadbalancer.server.port=3001"
volumes:
uptime-kuma:
networks:
proxy:
external: true
Enable "Trust Proxy"
In Settings → Reverse Proxy → Trust Proxy, set the option to Yes so that Uptime Kuma accepts the X-Forwarded-* headers from the proxy. Without this, all log entries show the internal Docker IP of the Traefik container instead of the real client IP.
Resource Consumption and Operational Limits
Measured Values on a VPS
Setup: 15 monitors (10 HTTP, 3 TCP, 2 Push), 60-second interval, VPS with 2 vCPUs (AMD EPYC), 4 GB RAM.
| Metric | Measured value |
|---|---|
| RSS idle | ~65 MB |
| CPU idle | < 0.1 % |
| Time to first poll after start | ~4 seconds |
Image size (louislam/uptime-kuma:2) |
~160 MB compressed |
The image is based on Node.js 22 on Debian Bookworm Slim. A 2-slim tag is available for environments where disk space is tight.
When Uptime Kuma Hits Its Limits
Above roughly 200 monitors with polling intervals under 30 seconds, the SQLite write bottleneck can reappear. At that scale, switching to Prometheus + Grafana is the right move. For a typical self-hosting environment with fewer than 100 services, Uptime Kuma is more than sufficient. Container-level health checks as a complementary layer are covered in Docker HEALTHCHECK and restart_policy: when containers heal themselves. For regular backups of the Kuma volume, Restic with encrypted backups to S3 is a natural companion.
Questions from the Field
Does Uptime Kuma run on a Raspberry Pi?
Yes. The official image is available for both amd64 and arm64. On a Pi with an SD card, set the environment variable UPTIME_KUMA_SQLITE_SINGLE_CONNECTION=1 to avoid SQLITE_BUSY errors from concurrent writes.
How do I back up the Kuma database? Mount the named volume into a temporary container and archive the directory with Restic. The volume contains only the SQLite file and stays small.
Can I run Uptime Kuma without a public domain?
Yes. Without Traefik, Kuma is accessible directly at http://<server-ip>:3001. Telegram notifications work regardless, as long as the host can make outbound HTTPS connections to the Telegram Bot API.
What happens if I lose my bot token?
Telegram bot tokens don't expire. Use @BotFather → /mybots → Regenerate token to get a new one; then update it in Kuma's notification settings.
Is there a public status page for external users? Yes. Under Status Page you can create a publicly accessible — or password-protected — page that shows the state of your services without a login. It can be served under its own subdomain.
What Comes Next
The complete installation guide and all configuration options are in the official wiki. The reverse proxy section additionally covers nginx, Caddy, and Apache alongside Traefik. For deeper metrics — CPU histograms, latency dashboards, AlertManager integration — Prometheus + Grafana is the logical next step; a dedicated post on that stack is coming soon. For questions about concrete infrastructure architecture, we're happy to help through our IT support service.
Note: The articles on this blog are produced with the help of AI and are editorially reviewed before publication. Editorial responsibility lies with Emre Yurtbay (see the Impressum).