Uptime Kuma is a self-hosted monitoring tool that gives engineering teams real-time visibility into service availability. Unlike SaaS uptime monitors that charge per check or require trusting a third party with your internal topology, Uptime Kuma runs entirely within your infrastructure. A single container watches HTTP endpoints, TCP ports, DNS records, databases, and more—and fires alerts over Slack, Telegram, email, PagerDuty, or any webhook you configure. Pairing it with Docker Compose and Caddy gives you an automatic HTTPS frontend, clean certificate renewal, and a reproducible stack you can version-control and redeploy in minutes.
Architecture and flow overview
The production stack consists of two containers and one reverse proxy binary, all defined in a single docker-compose.yml:
- uptime-kuma: Node.js service that stores state in a SQLite file at
/app/data. Exposes port 3001 internally. - Caddy: Lightweight reverse proxy that terminates TLS, redirects HTTP to HTTPS, and forwards requests to
127.0.0.1:3001.
Uptime Kuma uses a persistent SQLite database (no external DBMS required), so the only data volume you must back up is /opt/uptime-kuma/data. Caddy stores its ACME certificates in a named volume caddy_data. The two services share an internal bridge network; only Caddy is reachable from the host on ports 80 and 443.
Traffic flow: browser → Caddy :443 (TLS) → uptime-kuma:3001 → SQLite. Alert flow: monitor check → status engine → notification channel (Slack/Telegram/webhook etc.).
Prerequisites
- Ubuntu 22.04 or 24.04 server with a public IP
- DNS A record pointing your monitoring subdomain (e.g.,
status.yourdomain.com) to the server IP - Docker Engine 24+ and Docker Compose v2 installed
- Ports 80 and 443 open in UFW and any cloud security group
- Root or sudo access
Step-by-step deployment
1. Prepare the project directory
Create a dedicated directory with restricted permissions to hold all stack files and the persistent data volume:
mkdir -p /opt/uptime-kuma/data
chmod 700 /opt/uptime-kuma
cd /opt/uptime-kuma2. Create the Docker Compose file
Create /opt/uptime-kuma/docker-compose.yml. Uptime Kuma needs no database credentials, making this one of the simplest stacks to stand up:
version: "3.9"
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
volumes:
- ./data:/app/data
ports:
- "127.0.0.1:3001:3001"
networks:
- internal
caddy:
image: caddy:2-alpine
container_name: caddy-uptime
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- internal
volumes:
caddy_data:
caddy_config:
networks:
internal:
driver: bridge3. Create the Caddyfile
Create /opt/uptime-kuma/Caddyfile. Caddy will obtain and renew TLS certificates automatically via ACME:
status.yourdomain.com {
reverse_proxy uptime-kuma:3001
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
}
log {
output file /var/log/caddy/uptime-kuma.log
format json
}
}Replace status.yourdomain.com with your actual domain. Caddy uses the internal Docker service name uptime-kuma as the upstream because both containers share the internal bridge network.
4. Open firewall ports
Allow Caddy to handle inbound web traffic. The Uptime Kuma port is bound only to 127.0.0.1 inside the container network, so it is not reachable from outside:
ufw allow 80/tcp
ufw allow 443/tcp
ufw reload5. Start the stack
Pull images and start the services in detached mode:
docker compose up -d
docker compose logs -fCaddy will contact Let's Encrypt for a certificate on first start. This typically takes 10–30 seconds. Watch for certificate obtained successfully in the Caddy logs before accessing the dashboard.
6. First-time setup
Navigate to https://status.yourdomain.com in your browser. Uptime Kuma will present a setup wizard to create your admin account on first launch. Create a strong password and save it in your password manager—there is no password-reset flow without direct container access:
# If you lose admin access, reset via the CLI inside the container:
docker exec -it uptime-kuma node extra/reset-password.jsConfiguration and secrets handling
Uptime Kuma stores all configuration—monitors, notification channels, and credentials—inside the SQLite database at /opt/uptime-kuma/data/kuma.db. There are no plaintext secrets in environment files for a basic deployment, which simplifies the security posture. However, keep the following in mind:
- Notification channel tokens: Slack webhook URLs, Telegram bot tokens, and SMTP passwords are stored in the database. Treat the
data/volume as a secret and restrict its permissions to root-only (chmod 700 /opt/uptime-kuma/data). - Backup the database: Schedule a daily backup of
kuma.dbto off-server storage. A one-liner usingsqlite3:sqlite3 /opt/uptime-kuma/data/kuma.db ".backup '/backup/kuma-$(date +%F).db'". - Docker socket access: Uptime Kuma can monitor Docker containers directly if you mount
/var/run/docker.sock. Only do this on hosts where you trust the operator fully—socket access is effectively root on the host. - Authentication: Enable two-factor authentication (TOTP) inside the Uptime Kuma settings panel immediately after initial setup. The admin account is the only access gate by default.
Verification
After the stack is running, confirm the deployment is healthy:
# Check both containers are up
docker compose ps
# Check Caddy got a TLS certificate
curl -sv https://status.yourdomain.com 2>&1 | grep -E 'SSL|TLS|certificate'
# Confirm Uptime Kuma is responding
curl -o /dev/null -w "%{http_code}" https://status.yourdomain.com
# Tail Caddy logs for errors
docker compose logs caddy --tail=20Expected output: both containers show Up, the HTTPS request returns HTTP 200 or a redirect to the login page, and no certificate errors appear in Caddy's logs.
Common issues and fixes
- Caddy returns a self-signed cert or ACME error: Your DNS A record is not yet resolving to the server IP, or port 80 is blocked by a firewall/cloud security group. Caddy needs port 80 reachable for the HTTP-01 ACME challenge even if you serve only on 443.
- Uptime Kuma dashboard is blank after login: The container restarted and its data volume is empty. Check
docker compose psand verify the./databind-mount exists on disk and is not empty. - WebSocket disconnections in the dashboard: The Caddy upstream uses the plain HTTP reverse proxy, which drops WebSocket upgrade headers. Ensure you are using the
reverse_proxydirective without a trailing slash and that no custom@handleblock strips headers before proxying. - Container exits immediately with permission error: The
./datadirectory owner must be UID 1000 (the non-root user inside the container). Fix with:chown -R 1000:1000 /opt/uptime-kuma/data. - Notifications not reaching Slack or Telegram: Test the notification channel from Uptime Kuma's settings page. If the test succeeds but monitor alerts do not fire, check that the monitor's alert conditions (retry count, heartbeat interval) are met before the alert triggers.
- Uptime Kuma reports monitors as down immediately after adding them: Some targets (especially private IPs) are unreachable from the monitoring container. Use the network mode or ensure the target is accessible from the Docker bridge network.
FAQ
Can I monitor private/internal services that are not publicly reachable?
Yes. Because Uptime Kuma runs inside your own infrastructure, it can reach any host on your private network. Add the internal hostname or IP directly as a monitor target. No VPN or port-forwarding is required.
How do I add a public status page for my users?
Navigate to Status Pages in the left sidebar, click Add New Status Page, give it a slug (e.g., status), and assign monitors to it. The page is accessible at https://status.yourdomain.com/status/status without login. You can set a custom domain, logo, and theme from the status page settings.
How do I upgrade Uptime Kuma?
Pull the latest image and recreate the container. Your data is safe in the bind-mounted ./data volume:
docker compose pull
docker compose up -d
docker compose logs uptime-kuma --tail=20What happens to alerts if the server reboots?
Because both containers use restart: unless-stopped, they start automatically after a host reboot. Monitors resume within seconds. Caddy re-reads its certificate cache from the named volume and does not need to re-issue certificates on restart.
Can I run multiple Uptime Kuma instances across teams?
Yes—each team gets its own /opt/uptime-kuma-teamname directory, its own docker-compose.yml with a different internal port, and a different subdomain in the Caddyfile. The instances share the Caddy container or run independent Caddy instances depending on your preference.
How do I integrate Uptime Kuma with PagerDuty or OpsGenie for on-call escalation?
In Uptime Kuma's notification settings, select PagerDuty or OpsGenie from the provider list and paste your integration key. You can assign different notification channels per monitor so that, for example, a database heartbeat goes to PagerDuty on-call while a low-priority endpoint goes only to a Slack channel.
Is Uptime Kuma production-safe for monitoring critical infrastructure?
For most teams, yes. Uptime Kuma is stable, actively maintained, and used by many organizations in production. The main caveat is that it is a single-node service: if the monitoring host itself goes down, you lose visibility. For mission-critical monitoring, run a second independent instance on a separate cloud region or provider that watches the first one, creating a cross-check loop.
Internal links
- Production Guide: Deploy Vaultwarden with Docker Compose + Caddy on Ubuntu
- Production Guide: Deploy ntfy with Docker Compose + Caddy + Auth + Attachments on Ubuntu
- Production Guide: Deploy Healthchecks with Docker Compose + Caddy + PostgreSQL on Ubuntu
Talk to us
If you want this deployed with hardened access controls, monitoring standards, and production runbooks tailored to your environment, our team can help end-to-end.