Why Self-Host Your Uptime Monitoring?
Pingdom starts at $15/month for 10 monitors. Better Uptime charges per team member. StatusCake's free tier checks every 5 minutes at best. And all of them store your service health data on someone else's server.
Uptime Kuma is the alternative: a beautiful, self-hosted monitoring dashboard that checks HTTP/S endpoints, TCP ports, DNS records, push endpoints, Docker containers, and SSL certificate expiry — all from a single lightweight container. It sends alerts through 90+ notification channels including Slack, Discord, Telegram, PagerDuty, email, and webhooks. It generates public status pages. It costs nothing beyond your server.
This guide walks you through the full Uptime Kuma setup: Docker Compose, a production reverse proxy with HTTPS, every monitor type worth knowing, notification configuration, status pages, and a troubleshooting section for the gotchas that catch people out.
Prerequisites
Before you start, have these in place:
- A Linux server or VPS — Ubuntu 22.04/24.04 LTS recommended. Uptime Kuma is extremely lightweight: 512 MB RAM and 1 CPU core is sufficient for hundreds of monitors.
- Docker Engine 24+ and Docker Compose v2 — the
docker composeplugin. Verify withdocker compose version. - A domain name with an A record pointing to your server — e.g.,
status.yourdomain.com. Needed for HTTPS and for sharing public status pages. - Ports 80 and 443 open in your firewall for Let's Encrypt and HTTPS traffic.
- Basic Linux and Docker familiarity — comfortable editing YAML files and running SSH commands.
That's it. Uptime Kuma has no database dependency for basic use — it stores everything in a local SQLite file, making it trivially easy to back up and restore.
Step 1: Deploy Uptime Kuma with Docker Compose
The fastest way to get Uptime Kuma running is a single docker run command. For production — where you want persistent data, clean restarts, and easy management — use Docker Compose.
Quick Start (Single Command)
# Quick start — data stored in a named Docker volume
docker run -d \
--restart=always \
-p 3001:3001 \
-v uptime-kuma:/app/data \
--name uptime-kuma \
louislam/uptime-kuma:2
# Access at http://YOUR_SERVER_IP:3001
The first time you open the UI, you'll be prompted to create an admin account. Do this immediately — Uptime Kuma has no other initial access control.
Production Docker Compose Setup
For a managed, production-ready deployment, use Docker Compose with a bind mount so your data lives at a predictable path on the host:
uptime-kuma/
├── docker-compose.yml
└── data/ # Uptime Kuma data directory (auto-created)
# docker-compose.yml
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
restart: always
volumes:
- ./data:/app/data
# Optional: mount Docker socket to enable Docker container monitoring
# - /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- TZ=UTC # Set to your timezone, e.g. Europe/London or America/New_York
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001"]
interval: 30s
retries: 3
start_period: 10s
timeout: 5s
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"
labels:
- "traefik.enable=true"
- "traefik.http.routers.uptime-kuma.rule=Host(`status.yourdomain.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"
networks:
- traefik-net
networks:
traefik-net:
external: true
# Start the stack
docker compose up -d
# Follow startup logs
docker logs uptime-kuma -f --tail 50
# Confirm it's healthy
docker ps --filter name=uptime-kuma
Once running, navigate to https://status.yourdomain.com (if using Traefik) or http://YOUR_SERVER_IP:3001 for initial setup. Create your admin account on first load.
For alternative reverse proxy setups — Caddy for automatic HTTPS with zero config, or Nginx with Certbot — see our production-specific guides: Deploy Uptime Kuma with Docker Compose + Caddy on Ubuntu and Deploy Uptime Kuma with Docker Compose + NGINX + PostgreSQL on Ubuntu.
Step 2: Configure Your Reverse Proxy for WebSocket
Uptime Kuma is built on WebSocket for its live dashboard updates. This means your reverse proxy must pass the Upgrade and Connection headers — omitting them is the most common reason the dashboard loads but then shows stale data or connection errors.
Nginx Configuration
# /etc/nginx/sites-available/uptime-kuma
server {
listen 443 ssl http2;
server_name status.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/status.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/status.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:3001/;
proxy_http_version 1.1;
# Required for WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name status.yourdomain.com;
return 301 https://$host$request_uri;
}
After applying your proxy config, go to Settings → Security → Reverse Proxy → Trust Proxy and set it to Yes. This tells Uptime Kuma to trust X-Forwarded-* headers from the proxy so the correct client IPs appear in logs.
Running Podman instead of Docker? See our rootless deployment guide: Deploy Uptime Kuma with Rootless Podman + systemd + Caddy on Ubuntu.
Step 3: Add and Configure Monitors
Uptime Kuma supports a wide range of monitor types. Here's what each one does and when to use it.
HTTP(S) Monitor — Your Most-Used Type
Checks that a URL returns an expected HTTP status code. The most common monitor type for websites, APIs, and web services.
- URL — the full endpoint to check, e.g.,
https://api.yourdomain.com/health - Heartbeat Interval — how often to check. 60 seconds is a solid default; go to 30s for critical services
- Retries — how many consecutive failures before triggering an alert. Set to 2–3 to avoid false positives from transient blips
- Expected Status Code — defaults to any 2xx. Override to
200exactly for stricter health checks - Certificate Expiry — Uptime Kuma automatically checks TLS cert expiry on HTTPS monitors and alerts you before certificates expire
HTTP(S) Keyword Monitor
Like the HTTP monitor, but also checks that the response body contains (or doesn't contain) a specific string. Use this to verify that your app is actually serving the right content — not just returning 200 with an error page.
TCP Port Monitor
Checks that a TCP port is open and accepting connections. Use for databases, SMTP servers, SSH, Redis, or any non-HTTP service where you just need to confirm the port is reachable.
DNS Record Monitor
Resolves a DNS record and verifies the result matches an expected value. Catches DNS misconfigurations, propagation failures, and unauthorized record changes before your users do.
Push Monitor (Heartbeat from Inside)
The push monitor works in reverse: instead of Uptime Kuma reaching out to your service, your service pushes a heartbeat to Uptime Kuma. Uptime Kuma generates a unique URL; your service hits it on a schedule. If the pushes stop arriving, Uptime Kuma fires an alert.
This is ideal for:
- Services behind a firewall that Uptime Kuma can't reach directly
- Cron jobs or scheduled tasks — verify they're actually running
- Background workers and queue processors
# Example: push heartbeat from a cron job
# Uptime Kuma gives you a URL like:
# https://status.yourdomain.com/api/push/YOUR_UNIQUE_TOKEN
# Add to your crontab or script:
# */5 * * * * curl -fsS --retry 3 https://status.yourdomain.com/api/push/YOUR_UNIQUE_TOKEN > /dev/null
# Or from a Docker-based background worker:
curl -fsS --retry 3 \
"https://status.yourdomain.com/api/push/YOUR_UNIQUE_TOKEN?status=up&msg=OK&ping=" \
> /dev/null
Docker Container Monitor
Directly checks whether a named Docker container is running. Requires mounting the Docker socket into the Uptime Kuma container. Uncomment the socket line in your docker-compose.yml:
# In docker-compose.yml, uncomment the Docker socket volume
volumes:
- ./data:/app/data
- /var/run/docker.sock:/var/run/docker.sock:ro
# Security note: even with :ro (read-only), mounting the Docker socket
# grants the container significant host access. Only do this on
# trusted, purpose-built monitoring hosts.
# After remounting, restart the stack:
docker compose down && docker compose up -d
Step 4: Set Up Notifications
An uptime monitor without alerts is just a pretty dashboard. Uptime Kuma supports 90+ notification providers. Set up at least two — a primary channel and a fallback.
Add a Notification Provider
Go to Settings → Notifications → Add Notification. Pick your provider. The most commonly used ones:
- Slack — paste your Incoming Webhook URL
- Discord — paste your Discord Webhook URL
- Telegram — enter your Bot Token and Chat ID
- Email (SMTP) — enter your SMTP server details
- PagerDuty — paste your Integration Key for on-call escalation
- Webhook — POST to any URL when a monitor changes state
- Microsoft Teams — paste your Teams Incoming Webhook URL
Example: Slack Webhook Notification
# Uptime Kuma Slack notification config (set in the UI)
Notification Type: Slack
Name: #ops-alerts
Webhook URL: https://hooks.slack.com/services/T00000000/B00000000/XXXX
Username: Uptime Kuma
Icon Emoji: :rotating_light:
Channel: #ops-alerts
# Test the notification using the "Test" button before saving
# A test message confirms the webhook is working before a real incident fires
Assign Notifications to Monitors
Notifications are not globally applied — you assign them per monitor. When creating or editing a monitor, expand the Notifications section and toggle on the channels you want. This lets you have different alert routing per service: critical APIs might go to PagerDuty, while minor internal tools only ping Slack.
Alert Timing Settings
For each monitor you can configure:
- Retries before alert — how many consecutive failures trigger a notification. Default is 1; set to 2–3 for HTTP monitors to filter transient errors
- Resend notification every X hours if still down — sends a reminder alert if an outage continues. Useful for long-running incidents
- Certificate expiry notification — alerts N days before your TLS cert expires. Default is 7 days; consider setting 30 days for critical domains
Step 5: Build a Public Status Page
A status page lets your users check service health themselves instead of emailing your support queue. Uptime Kuma's built-in status pages are clean, fast, and support custom domains.
Create a Status Page
- Click Status Page in the left sidebar
- Click New Status Page
- Set a slug (e.g.,
status) — this becomes the URL path - Set a title and optional description
- Add monitors to the page by dragging them in
- Group related monitors under headings (e.g., "API", "Database", "CDN")
Your status page is immediately available at https://status.yourdomain.com/status/status (the slug you set). Share it with customers or link to it from your app's footer.
Custom Domain for the Status Page
To serve the status page at a clean URL like https://status.yourdomain.com directly (no subpath), configure a custom domain in the status page settings and point a CNAME or A record at your Uptime Kuma server. Uptime Kuma handles the routing internally.
Maintenance Windows
Schedule planned downtime under Maintenance in the sidebar. During a maintenance window, Uptime Kuma suppresses alerts for the affected monitors and shows a "Maintenance" status on the status page instead of "Down". This prevents your on-call team from being woken up during planned work.
Step 6: Production Hardening
A default Uptime Kuma deployment works well. Here are the changes worth making before you rely on it for production alerting.
Back Up the Data Directory Daily
Everything in Uptime Kuma — monitor config, notification settings, status pages, history — lives in ./data/kuma.db (SQLite). Back it up:
# Simple daily backup script — add to crontab
#!/bin/bash
BACKUP_DIR="/backups/uptime-kuma"
DATA_DIR="/opt/uptime-kuma/data"
DATE=$(date +%Y-%m-%d)
mkdir -p "$BACKUP_DIR"
# SQLite WAL checkpoint before backup for consistency
docker exec uptime-kuma sqlite3 /app/data/kuma.db ".checkpoint FULL" 2>/dev/null || true
# Copy the data directory
cp -r "$DATA_DIR" "$BACKUP_DIR/kuma-$DATE"
# Keep only the last 14 days
find "$BACKUP_DIR" -maxdepth 1 -name "kuma-*" -mtime +14 -exec rm -rf {} +
echo "Backup complete: $BACKUP_DIR/kuma-$DATE"
Keep the Image Pinned and Updated
# Check current version
docker exec uptime-kuma node -e "console.log(require('./package.json').version)"
# Pull the latest v2 image and recreate
docker compose pull uptime-kuma
docker compose up -d uptime-kuma
# Watch logs after upgrade to confirm it started cleanly
docker logs uptime-kuma -f --tail 30
Production Hardening Checklist
- Enable Trust Proxy — Go to Settings → Security → Trust Proxy → Yes after setting up your reverse proxy. Without this, Uptime Kuma logs the proxy IP instead of the real client IP.
- Set up 2FA — Uptime Kuma v2 supports TOTP two-factor authentication. Enable it under Settings → Security → Two Factor Authentication. Do this now — the admin account is the only line of defence.
- Restrict dashboard access — The dashboard at
/shows all your infrastructure. If you don't need it public, restrict it to your IP at the reverse proxy level. Only the status page needs to be publicly accessible. - Don't expose port 3001 publicly — Your reverse proxy should be the only entry point. After Traefik or Nginx is set up, block port 3001 in your firewall:
ufw deny 3001. - Monitor the monitor — Use a separate external check (e.g., a free tier check on Better Uptime or a second Uptime Kuma instance) to alert you if your Uptime Kuma instance itself goes down. A monitoring tool that's silently offline is worse than none at all.
- Set retention limits — Go to Settings → General → Keep monitor history and set a reasonable limit (e.g., 180 days). Unbounded history grows the SQLite database indefinitely.
Tips and Troubleshooting
Dashboard loads but shows no live data or spinner freezes
This is the WebSocket problem. Your reverse proxy isn't passing the Upgrade and Connection: upgrade headers. Double-check your Nginx or Traefik config and confirm both headers are set. With Traefik, WebSocket pass-through is automatic — but if you've added custom middleware that strips headers, that's your culprit.
Monitors show "Down" immediately after adding them
Check the monitor's log by clicking on it in the dashboard. Common causes:
- The URL or port is unreachable from the Uptime Kuma container's network — not from your laptop
- The endpoint requires a specific Host header or client certificate
- The server returns a non-2xx status code (e.g., 301 redirects are not followed by default)
- Firewall rules blocking outbound connections from the server
# Test connectivity from inside the container
docker exec uptime-kuma curl -v https://api.yourdomain.com/health
# Test TCP port reachability from inside the container
docker exec uptime-kuma nc -zv your-db-host 5432
# Check container DNS resolution
docker exec uptime-kuma nslookup yourdomain.com
Push monitor fires alerts even though the service is healthy
The push monitor fires when it stops receiving heartbeats within the expected interval. Check that your cron job or script is actually running on schedule. Use crontab -l and check the system cron log (grep CRON /var/log/syslog) to verify execution. Also confirm the push URL hasn't changed — regenerating the token in Uptime Kuma creates a new URL.
Notifications not arriving
Use the Test button on each notification provider in Settings → Notifications. If the test succeeds but real alerts don't arrive:
- Confirm the notification is assigned to the monitor (check the monitor's notification settings)
- Check the retry count — alerts only fire after N consecutive failures. A single blip won't trigger an alert if retries is set to 3
- Check your Slack/Discord webhook for rate limiting or deactivation
Database grows too large / performance degrades
SQLite handles thousands of monitors well, but unbounded history accumulates. Set a history retention limit under Settings → General. If the database is already large, you can compact it:
# VACUUM the SQLite database to reclaim space and defragment
# Run while Uptime Kuma is stopped for safety
docker compose stop uptime-kuma
sqlite3 ./data/kuma.db "VACUUM;"
docker compose start uptime-kuma
# Check database size before and after
ls -lh ./data/kuma.db
Uptime Kuma crashes or won't start after an update
Check the logs first: docker logs uptime-kuma --tail 100. Common causes after an update are database migration failures. If a migration fails, restore from your most recent backup of ./data/kuma.db and retry with the previous image version. Never skip major versions without checking the release notes.
What to Do Next
You now have a self-hosted uptime monitoring stack that covers your infrastructure end-to-end. Here's where to take it further:
- Add a monitor for every service — HTTP health endpoints, TCP ports for databases, DNS monitors for your domains, push monitors for your cron jobs. The more coverage you have, the faster you find problems.
- Build customer-facing status pages — A public status page reduces support tickets during incidents. Customers who can self-serve know you're aware of an issue.
- Set up PagerDuty or OpsGenie escalation — For production services, route critical alerts to an on-call rotation tool. Uptime Kuma has native PagerDuty and OpsGenie integrations.
- Add certificate expiry monitoring — Every HTTPS monitor automatically checks cert expiry. Make sure all your HTTPS services are monitored and alert windows are set to 30+ days.
- Scale to PostgreSQL for larger teams — Uptime Kuma v2 adds MariaDB/MySQL support. For teams with hundreds of monitors and multiple contributors, moving off SQLite improves concurrency and backup ergonomics. See our Uptime Kuma + NGINX + PostgreSQL production guide for the full setup.
Need Enterprise Monitoring Infrastructure?
Self-hosting Uptime Kuma for a small team is genuinely easy. Scaling it to cover a large infrastructure — with high availability, external alerting integrations, team-based access controls, and zero-downtime maintenance — is a different story. If your team needs help designing and running production observability infrastructure, Sysbrix can help.
Talk to our team → We help engineering teams build reliable, self-hosted monitoring and observability stacks — so you know about problems before your users do.