how to setup uptime kuma with docker
Run Uptime Kuma for Docker to monitor websites, ports, and DNS. Use a separate VPS for alerts via Telegram or email so you no go miss any downtime.
Wetin you dey build
One small container wey go dey watch your other servers and websites from outside. E go tell you immediately one of dem stop to answer, via email, Telegram, Discord, or webhook. Uptime Kuma na one Node process wey dey use SQLite file, so e dey run well for 256-512 MB of RAM. E go give you live dashboard, history graphs, and public status page. The install na just ten-line Compose file; the part wey really matter na where you run am and if your alerts don work for test, because monitor wey you never test say e fit reach you bad pass none at all: e go make you feel say you dey safe while you dey watch nothing.
Run the monitor for one place wey outage no fit reach am
Dis decision na him dey make or break di whole thing, so na him wey must come first. No run Uptime Kuma for di same box wey di things wey e dey watch dey run. If di monitor dey di same server wey e dey monitor, di exact problem wey you dey watch—like server die or memory finish—go kill di monitor too. You no go get any alert at all: silence from dead monitor dey look exactly like "everything dey fine." One other small trap wey dey exist even when di box still dey alive: monitor wey dey point to localhost dey share CPU with di workload. So, if load spike, di monitor check go timeout and e go change di target to down. Dis na false alarm, even though real users dey get service fine.
So, run Uptime Kuma for one different VPS from di one wey e dey watch. Ideally, use different provider or region, so e fit reach your services di same way your users dey reach dem: over di public internet, by hostname. One cheap instance dey enough, and one small monitoring VPS fit watch all your servers. To catch if Kuma itself die, add one push heartbeat from one cron elsewhere.
Prerequisites and sizing
- Fresh Ubuntu 24.04 VPS wey get Docker Engine and Compose v2 plugin. Make sure say you install am from Docker apt repository, no be the
docker.iodistro package, because that one dey slow. - 256 MB RAM fit run small number of monitors; 512 MB to 1 GB na better size if you wan run dozens plus the reverse proxy, and CPU no go work hard between checks.
- Domain and DNS
Arecord (likestatus.example.comwey point to VPS), only if you want TLS and public status page. If you wan run private instance, you fit skip DNS and use VPN or SSH tunnel. - Outbound network wey go reach wherever alerts dey go: SMTP for your mail provider, or HTTPS for Telegram and Discord.
The Compose file
Put dis one inside /srv/uptime-kuma/compose.yaml.
services:
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
restart: unless-stopped
ports:
- "127.0.0.1:3001:3001"
volumes:
- kuma-data:/app/data
volumes:
kuma-data:Run am and watch how e go start for first time:
sudo mkdir -p /srv/uptime-kuma
# save the file above as /srv/uptime-kuma/compose.yaml, then:
cd /srv/uptime-kuma && sudo docker compose up -d
sudo docker compose logs -f uptime-kumaIf e start well, e go log Listening on 3001 den e go quiet. Three things for dis file na deliberate choice.
127.0.0.1:3001:3001, no be 3001:3001. Docker dey publish ports with DNAT rules wey dem dey check before ufw even see de packet. So, if you just use 3001:3001, your dashboard go open for public internet regardless of your firewall. If you bind am to loopback, e go stay private, and na only de reverse proxy dey open; private instance fit skip de proxy and reach 3001 through self-hosted WireGuard VPN instead.
A named volume for /app/data. Everything wey Uptime Kuma dey remember—de SQLite database, your monitors, notification settings, and status-page logos—all dem dey stay dere. If you lose am, you go start from empty admin screen; na de only thing wey you must back up.
De image dey pin to major tag, :2. Na dat one be current stable line; check Docker Hub for de newest major before you copy am. No ever use moving tag like latest, because de project no dey recommend am again. If de major-version change for dis image, e go do one-way database migration; you must do dat one by choice, no be by accident when you dey do routine pull.
One warning: /app/data must dey sit for filesystem wey get POSIX file locks. Local Docker volume dey okay; for NFS, de SQLite database go corrupt and you go get SQLITE_BUSY and database disk image is malformed, so no ever use network share.
First run: create the admin account
Open your instance through your proxy for https://status.example.com, or use SSH tunnel: run ssh -L 3001:127.0.0.1:3001 user@your-vps and open http://localhost:3001. The first page na setup form for administrator username and password; no default login dey. Pick real password: this dashboard dey see internal addresses and tokens for everything wey you dey monitor. If you forget am later? Reset am from the host, no be from the browser:
sudo docker compose exec uptime-kuma npm run reset-passwordAdd your notification channels first, and test them
Set up your alerts before you start adding monitors. This way, you fit attach one channel as you dey create each monitor. Go to Settings then Notifications then Setup Notification, and use the Test button for each channel to make sure message land. If you no test notification, e dey easy for setup to fail without you knowing.
Email (SMTP). Fill in host, port, encryption, username, password, From, and To. Two working combinations na 465 with "Secure" set to TLS/SSL, or 587 with STARTTLS. For Gmail and most providers wey get two-factor auth, you must generate app password; normal account password go give you Error: Invalid login: 535-5.7.8 Username and Password not accepted.
Telegram. Message @BotFather, send /newbot, and copy the bot token. To get your chat ID, message the new bot once, open https://api.telegram.org/bot<token>/getUpdates, and read chat.id from the JSON. If you never message bot before, the getUpdates go empty and e no go get place to send message.
Discord. Inside the channel, open Edit Channel then Integrations then Webhooks then New Webhook, copy the URL, and paste am as Discord notification.
Generic webhook. For any other thing, like Slack incoming webhook, custom endpoint, or home-automation hook, the Webhook type dey POST JSON payload to the URL wey you give am. The Apprise integration wey dey inside cover most of the ninety-odd other services for the list.
Add monitors, one type at a time
Click Add New Monitor, pick one type, and set the Friendly Name, the Check Interval (60 seconds na better setting), the Retries (how many times e must fail before e show "down"; use 2 or 3 so one single dropped packet no go alarm you), and the notifications wey go fire. These na the types wey you go use:
- HTTP(s). Full URL. "Up" mean say status code dey correct (200-299 na default; you fit change am under Accepted Status Codes if
301or401na normal for your side). Na this one be main tool for websites and APIs. - HTTP(s) - Keyword. Same request as before, but "up" mean say one string must dey inside the body, or if Invert no dey, the string must no dey. This one dey catch when site dey return
200 OKeven as e dey show "Error establishing a database connection", which plain HTTP check go think say everything dey fine. - TCP Port. Bare TCP connection to one host and port, for things wey no be HTTP: SSH on 22, Postgres on 5432, SMTP server on 25, or game server.
- Ping. ICMP echo: e dey check if host dey reach and how latency be. But many networks and cloud firewalls dey block ICMP, so if ping monitor turn red, e fit mean "host down" or "provider dey block ping"; use TCP monitor take confirm.
- DNS. E dey resolve one record (A, AAAA, MX, TXT and so so) against one resolver wey you name, and e fit check if the answer correct, so you fit catch registrar or DNS outage early.
- Push. This one na "inside-out" monitor, we go talk about am next.
Monitoring a cron job with a push (heartbeat) monitor
Every monitor wey we talk above dey reach inside your service from outside. Push monitor dey work for different way: Uptime Kuma go dey wait, and your job go call am say "I don run." Na only this way dey true for backup or cron: HTTP check fit know say URL dey respond, but only the job go know if e finish well.
Create monitor wey get type Push. Uptime Kuma go generate unique URL like:
https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=OK&ping=Set the Heartbeat Interval to the time wey the job dey run, plus small extra time. Then add one line for the end of the script, so e go only fire if the job success:
#!/usr/bin/env bash
set -euo pipefail
# ... your backup or job runs here; set -e aborts on any failure ...
curl -fsS --retry 3 "https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=backup+ok&ping="If the job fail, set -e go abort before the curl; if the box down, the script no go run at all. Either way, the heartbeat go stop, and once the interval-plus-retries window finish, Uptime Kuma go change the monitor status to down and send alert to you. Treat that push token like secret: anybody wey get am fit pretend say the job dey run well.
Build a public status page
Status page na wetin customers dey see: e go show dem which services dey work and their recent history, without dem seeing your dashboard. Go to Status Pages then New Status Page, give am name and slug (the public path, like /status/main), drag the monitors wey you want into groups like "Websites" and "APIs", add logo and small description, then click Save. You fit also bind the page to its own domain so status.example.com go serve am directly.
Two things to watch out for: only add monitors wey you wan make dem public, because status page go show dem say the service dey exist and if e dey work; and your dashboard go stay behind your login, while the status page na public thing wey no need auth.
Put am behind reverse proxy with TLS, and watch out for websockets
If you wan use public instance, put reverse proxy for front of the loopback-bound container so you fit get TLS and hostname. One detail wey dey trip everybody up: Uptime Kuma UI na live Socket.IO app, so the proxy must upgrade the WebSocket connection. If you miss am, the page go load but e no go connect; the dashboard go just dey show "Connecting...", live heartbeats no go update, and browser console go show WebSocket connection to 'wss://.../socket.io/...' failed.
Install nginx and certbot, then write the vhost wey go proxy to the loopback port. Put am on port 80 for now and let certbot add TLS later; we cover the challenge, renewal timer, and how e dey fail for issuing Let's Encrypt certificates with certbot and nginx.
sudo apt install -y nginx certbot python3-certbot-nginxSave this one as /etc/nginx/sites-available/status.example.com; the two WebSocket lines na dem be the important ones:
server {
listen 80;
server_name status.example.com;
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-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
}Enable the site, test the config, then let certbot rewrite the block to listen on 443, put the certificate, and add HTTP-to-HTTPS redirect:
sudo ln -s /etc/nginx/sites-available/status.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d status.example.comThe Upgrade and Connection "upgrade" pair na dem be the main thing, and proxy_read_timeout 3600s dey stop nginx from dey cut the long-lived socket; certbot dey copy both into the 443 block wey e dey generate. If you don dey run many containers behind one proxy already, routing them through Traefik with automatic TLS dey do the same thing with container labels and e dey forward WebSocket upgrades by default.
No use basic-auth lock the whole vhost, because that one go lock out the public status page and the /api/push endpoint. Use Uptime Kuma built-in login, add fail2ban watching for repeated failed logins if the thing dey face internet, and if you no need make the dashboard public, just drop the proxy and reach am over a VPN.
How to monitor certificate expiry properly
HTTP(s) monitor fit warn you before TLS certificate expire: tick Certificate Expiry Notification and Uptime Kuma go send alert for the number of days wey you set. Two mistakes fit make am fail. Monitor by hostname, not IP, because if request no get SNI, e go get server default certificate and you go see Hostname/IP does not match certificate's altnames. Also, no tick Ignore TLS/SSL Error for monitor wey you want expiry warnings from: that toggle na for self-signed internal hosts (unable to verify the first certificate, DEPTH_ZERO_SELF_SIGNED_CERT), but e go stop Uptime Kuma from check certificate at all, including expiry.
Backups: na one directory
Because everything dey inside /app/data, backup na copy of that volume wey dem take when container stop, so SQLite file go dey consistent:
cd /srv/uptime-kuma
sudo docker compose stop
sudo docker run --rm \
-v uptime-kuma_kuma-data:/data \
-v /var/backups/kuma:/backup \
alpine tar czf /backup/kuma-$(date -u +%Y%m%dT%H%M%SZ).tgz -C /data .
sudo docker compose startFirst, use docker volume ls | grep kuma check the real name of the volume, because Compose dey add project directory prefix for front. After that, copy the tarball commot for the box, because if backup dey inside the same VPS, na just copy, no be backup. To restore, do the reverse: stop the stack, extract am inside empty /app/data volume, then start am.
Upgrades
Upgrades na image pull:
cd /srv/uptime-kuma
sudo docker compose pull
sudo docker compose up -dNew container go run any database migration when e first start; watch docker compose logs -f. Make you take the backup wey dey above before you pull, and make sure say you stay within one major tag: moving from :1 to :2 na one-way migration, so back up first and check the release notes.
Failure modes, with the strings you will see
False "down" on a monitor pointed at localhost. Monitor go turn red with timeout of 48000ms exceeded or connect ETIMEDOUT, but service still dey answer from your laptop. If you point monitor to the same host wey Uptime Kuma dey run, CPU or memory spike don kill the check, no be the target. Move the monitor to another VPS and point am to the public hostname.
connect ECONNREFUSED 127.0.0.1:443 (or any port). Nothing dey listen for that port: either the service don down, or you dey monitor localhost from inside the container, where 127.0.0.1 na the container itself, no be your server. Monitor the public hostname, no use loopback.
Invalid login: 535-5.7.8 Username and Password not accepted for email test. The SMTP credentials wrong, or the provider want app-specific password but you give dem your account password. Generate app password and paste am.
connect ETIMEDOUT or queryA ETIMEDOUT <host> for email test. Wrong port, or the provider dey block outbound SMTP. Confirm 465 or 587 match the Secure/STARTTLS setting, and test from the host with nc -vz smtp.example.com 587. Many providers dey block outbound 25 and some dey block submission ports until you ask dem.
self signed certificate or unable to verify the first certificate for email test. Your SMTP server dey show certificate wey Node no go trust; fix the mail server certificate instead of to dey cover am up.
Dashboard stuck on "Connecting...", console shows WebSocket connection ... failed. The reverse proxy no dey upgrade the WebSocket. Add the Upgrade and Connection "upgrade" headers for nginx, or use proxy wey dey forward dem by default like Traefik or Caddy. HTML dey load because na normal HTTP GET; na only the live socket need the upgrade.
Cert-expiry monitor never warns, or warns wrongly. Either you don tick Ignore TLS/SSL Error, wey dey disable cert checking, or the monitor dey point to IP and e dey read wrong certificate because SNI missing, wey dey show Hostname/IP does not match certificate's altnames. Untick ignore, monitor by hostname.
SQLITE_BUSY or database disk image is malformed for the logs. The /app/data volume dey for filesystem wey no get proper file locking, usually NFS; move am to local Docker volume and restore from backup.
FAQ
Where I go run my uptime monitor?
Run am for different server from the ones wey e dey watch. Better pass if you use another provider or region, wey go reach dem by hostname over public internet just as your users dey do. If the monitor dey share box with the targets, any outage wey kill server go kill monitor too. Also, if host dey overload, e fit cry "down" about services wey dey fine. Small separate VPS go solve both of these wahala.
How I go get alerts for Telegram or email?
Add the channel under Settings then Notifications, then attach am to each monitor. For Telegram, create bot with @BotFather and read chat.id from https://api.telegram.org/bot<token>/getUpdates; for email, use 465 for SSL or 587 for STARTTLS with app password if your provider dey use two-factor auth. Press Test and make sure say message arrive before you rely on am.
Uptime Kuma fit monitor cron job or backup script?
Yes, na that one be Push monitor: Uptime Kuma go give you URL and you curl am at the end of the script so e go fire only when work succeed. If the job fail or the box down, the heartbeat no go reach, and e go alert you after the interval pass. Na the only reliable way to know if scheduled job really run, because external check no fit see inside am.
Uptime Kuma vs Zabbix, which one I suppose run?
Uptime Kuma dey answer "is it up, from the outside, and did it alert me" for ten minutes with almost no resources, plus e get status page. E no dey collect deep metrics like CPU, memory and disk trends or fleet-wide thresholds; for that one, a full Zabbix monitoring server na the heavier, agent-based tool, and many people dey run both. You still dey decide wetin to run? our roundup of what to self-host in 2026 go help you see the context.