Uptime Kuma: Monitor Website, Port and DNS for Free
Run Uptime Kuma with Docker for website, port, DNS and cron monitoring. Get alerts through email or Telegram, plus a public status page from another VPS.
Wetin you dey build
Na one small container wey dey monitor your other servers and websites from outside, then tell you immediately when any one stop to answer, through email, Telegram, Discord or a webhook. Uptime Kuma na one Node process wey SQLite file dey support, so e fit run comfortably with 256-512 MB of RAM, and e give you live dashboard, history graphs and public status page. Installation na ten-line Compose file; the part wey really matter na where you run am and whether your alerts don ever fire for test, because monitor wey you never prove say e fit reach you worse pass no monitor at all: e go make you feel say you dey covered while e no dey watch anything.
Run the monitor for somewhere wey outage no fit reach am
This decision fit determine whether the whole setup go work or fail, so make we start with am. No run Uptime Kuma for the same server as the things wey e dey monitor. If monitor dey for the server wey e dey monitor, the exact event wey you care about, like the server going down or running out of memory, go kill the monitor too. Then you no go receive any alert: silence from dead monitor go look exactly like "everything is fine." Another hidden problem fit happen while server still dey alive: monitor wey point to localhost dey share CPU with the workload. So load spike fit make the monitor check time out and mark the target as down, even though na false alarm and real users still dey use the service fine.
So run Uptime Kuma for a different VPS from the one wey e dey monitor. If possible, use different provider or region. Make e reach your services the same way your users dey reach dem: through public internet, by hostname. One cheap instance dey enough, and one small monitoring VPS fit monitor all your servers. This separation dey most important for heavy apps wey you host. For example, PhotoPrism or Immich photo library fit keep CPU busy for hours while e dey index new import. Monitor wey share that hardware fit report say service dey down, even though e only dey busy. To detect when Kuma itself die, add a push heartbeat from cron for another place.
Prerequisites and sizing
- Fresh Ubuntu 24.04 VPS wey get Docker Engine and Compose v2 plugin. Install dem from Docker own apt repository, no be the
docker.iodistro package, because e dey lag. - 256 MB RAM fit run small number of monitors. 512 MB to 1 GB comfortable for dozens of monitors plus the reverse proxy. CPU use dey almost idle between checks.
- Domain and DNS
Arecord, for examplestatus.example.comwey point to the VPS, only if you want TLS and public status page. Private instance fit skip DNS and use VPN or SSH tunnel. - Outbound network access to anywhere alerts go: SMTP to your mail provider, or HTTPS to Telegram and Discord.
The Compose file
Put this 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:Start am and monitor the first boot:
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 startup correct, log go show Listening on 3001, then e go quiet. Three things for that file na deliberate.
127.0.0.1:3001:3001, no be 3001:3001. Docker dey publish ports with DNAT rules wey e evaluate before ufw ever see the packet. So plain 3001:3001 go put your dashboard for public internet, no matter wetin your firewall do. If you bind am to loopback, e remain private, and na only the reverse proxy dey exposed. For private instance, you fit skip the proxy and reach 3001 through self-hosted WireGuard VPN instead.
Named volume for /app/data. Everything wey Uptime Kuma dey remember, including the SQLite database, your monitors, notification settings, and status-page logos, dey there. If you lose am, you go start from empty admin screen. Na only this thing you must back up.
The image dey pinned to major tag, :2. Na the current stable line be that. Check Docker Hub for the newest major before you copy am. Never follow moving tag like latest, because the project dey deprecate am. Major-version jump for this image na one-way database migration. You need trigger am deliberately, no be make e happen by mistake during normal pull.
One thing to note: /app/data must dey on filesystem wey support POSIX file locks. Local Docker volume dey okay. For NFS, the SQLite database fit corrupt, and you go get SQLITE_BUSY and database disk image is malformed. So never use network share.
First run: create the admin account
Open the instance through your proxy for https://status.example.com, or use an 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 the administrator username and password; no default login dey. Choose a strong password: this dashboard dey see the internal addresses and tokens of everything wey you monitor. 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 alerts before you add monitors, so you fit attach one channel as you create each monitor. Go to Settings then Notifications then Setup Notification, and use each channel Test button to confirm say the message reach, because notification wey you never test na the second most common reason setup fit fail silently.
Email (SMTP). Fill host, port, encryption, username, password, From, and To. The two combinations wey dey work 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 an app password; normal account password go return Error: Invalid login: 535-5.7.8 Username and Password not accepted.
Telegram. Message @BotFather, send /newbot, then copy the bot token. To get your chat ID, message the new bot once, open https://api.telegram.org/bot<token>/getUpdates, then read chat.id from the JSON. Bot wey you never message first go get empty getUpdates and e no get where to send message.
Discord. For the channel, open Edit Channel then Integrations then Webhooks then New Webhook, copy the URL, then paste am as a Discord notification.
Generic webhook. For anything else, like Slack incoming webhook, custom endpoint, or home-automation hook, the Webhook type go POST JSON payload to URL wey you provide, and the bundled Apprise integration covers most of the ninety-odd other services for the list. If you prefer make no third party dey between outage and your phone, choose the built-in ntfy type and point am to ntfy server wey you dey run yourself, wey go push message to your handset through channel wey you control from end to end.
Add monitors, one type at a time
Click Add New Monitor, pick one type, then set the Friendly Name, the Check Interval (60 seconds make sense), the Retries (how many failures go one after another before e mark am "down"; use 2 or 3 so one dropped packet no go trigger page), and the notifications wey go fire. Na these types you go use:
- HTTP(s). Na full URL. E dey up when status code wey e accept return (200-299 by default; you fit widen am under Accepted Status Codes if
301or401dey normal for you). Na your main check for websites and APIs. - HTTP(s) - Keyword. Na the same request, but for e count as "up", one string must dey inside the body, or, if you use Invert, must no dey inside. This one catch case where site return
200 OKbut display "Error establishing a database connection", wey plain HTTP check go call healthy. E also correct for browser front end wey dey talk to separate backend, like Halcyon video-store skin wey dey run on top Jellyfin, where page shell fit happily return200while media server behind am no dey reachable. - TCP Port. Na bare TCP connection go host and port, for things wey no be HTTP: SSH for 22, Postgres for 5432, SMTP server for 25, or game server.
- Ping. Na ICMP echo: cheap way to check reachability and latency. But plenty networks and cloud firewalls dey drop ICMP, so red ping monitor fit mean "host down" or "provider blocks ping"; confirm am with TCP monitor.
- DNS. E resolve record (A, AAAA, MX, TXT and so on) against resolver wey you name, and e fit confirm the answer. This one fit catch registrar or DNS outage early.
- Push. Na inside-out monitor; we go cover am next.
Monitoring cron job with push (heartbeat) monitor
Every monitor wey dey above dey check your service from outside. Push monitor dey work the other way: Uptime Kuma dey wait, and your job go call am to talk say “I run.” Na only this method dey honestly monitor backup or cron job: HTTP check fit know say URL respond, but na the job itself know whether e complete.
Create monitor wey get type Push. Uptime Kuma go generate unique URL like this:
https://status.example.com/api/push/j8Xa2Kd9Qe?status=up&msg=OK&ping=Set Heartbeat Interval to how often the job dey run, plus small extra time. Then add one line for the end of the script, so e go run only when the job succeed:
#!/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 stop am before curl; if the server dey down, the job no go run too. For both cases, the heartbeat go stop. After the interval-plus-retries window pass, Uptime Kuma go change the monitor to down and alert you. Treat that push token like secret: anybody wey get am fit fake healthy heartbeat.
Build public status page
Status page na the view customers go see: which services dey up and their recent history, without exposing your dashboard. Go Status Pages then New Status Page, give am a name and slug (the public path, like /status/main), drag the monitors wey you want enter groups like "Websites" and "APIs", add logo and short description, then Save. You fit also bind the page to its own domain so status.example.com go serve am directly.
Two things to watch: only add monitors wey you ready to make public, because status page dey show say service dey exist and whether e dey up; and dashboard still dey behind your login, while status page intentionally dey public and no auth dey needed.
Put am behind reverse proxy with TLS, and no forget websockets
For public instance, put reverse proxy in front of the loopback-bound container for TLS and hostname. The detail wey dey confuse everybody be say: Uptime Kuma UI na live Socket.IO app, so proxy must upgrade the WebSocket connection. If you miss am, page go load but e no go connect; dashboard go remain for "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 for port 80 for now, then make certbot add TLS afterward; challenge, renewal timer and failure modes dey covered for how to issue Let's Encrypt certificates with certbot and nginx.
sudo apt install -y nginx certbot python3-certbot-nginxSave this as /etc/nginx/sites-available/status.example.com; na the two WebSocket lines matter pass:
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 make certbot rewrite the block to listen on 443, add the certificate and put 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 the main thing, and proxy_read_timeout 3600s stops nginx from tearing down the long-lived socket; certbot go copy both enter the 443 block wey e generate. If you already dey run several containers behind one proxy, route dem through Traefik with automatic TLS fit do the same thing with container labels and forward WebSocket upgrades by default.
No use basic-auth for the whole vhost, because that one go also lock public status page and the /api/push endpoint. Keep Uptime Kuma built-in login, add make fail2ban monitor repeated failed logins if e dey face internet, and if dashboard no need make e public, remove the proxy and access am through VPN.
Certificate expiry monitoring wey correct
HTTP(s) monitor fit also warn you before TLS certificate expire: tick Certificate Expiry Notification, and Uptime Kuma go alert you some set number of days before expiry. Two mistakes fit make am read the certificate wrong. Monitor by hostname, not IP, or request wey no get SNI go receive the server default certificate, and you go see Hostname/IP does not match certificate's altnames. And no tick Ignore TLS/SSL Error for monitor wey you want use get expiry warnings: that toggle na for self-signed internal hosts (unable to verify the first certificate, DEPTH_ZERO_SELF_SIGNED_CERT), but e stop Uptime Kuma from checking the certificate at all, including expiry.
Backups: e dey one directory
Because everything dey inside /app/data, backup na copy of that volume wey you take while container stop, so the SQLite file go remain 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 confirm the real name of the volume with docker volume ls | grep kuma, because Compose dey add project directory as prefix. Then copy the tarball comot from the box, because backup wey dey for the same VPS na only copy, e no be proper backup. To restore am, na reverse process: stop the stack, extract the files 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 -dThe new container go run any database migration for the first start; monitor docker compose logs -f. Take the backup wey dey above before you pull, and stay within one major tag: moving from :1 go :2 na one-way migration, so back up first and check the release notes.
Failure modes, plus the strings wey you go see
False "down" for monitor wey dey point localhost. Monitor go turn red with timeout of 48000ms exceeded or connect ETIMEDOUT, but service still dey answer from your laptop. If e dey target the same host wey Uptime Kuma dey run on, CPU or memory spike don starve the check, no be target problem. Move the monitor go separate VPS and target the public hostname.
connect ECONNREFUSED 127.0.0.1:443 (or any port). Nothing dey listen for that port: either service dey down, or you monitor localhost from inside container, where 127.0.0.1 na the container, no be your server. Monitor the public hostname, no be loopback.
Invalid login: 535-5.7.8 Username and Password not accepted for email test. SMTP credentials no correct, or provider dey require app-specific password but you enter your account password. Generate app password and paste am.
connect ETIMEDOUT or queryA ETIMEDOUT <host> for email test. Port no correct, or provider dey block outbound SMTP. Confirm say 465 or 587 match the Secure/STARTTLS setting, and test from host with nc -vz smtp.example.com 587. Plenty 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 present certificate wey Node no go trust; fix the mail server certificate instead of covering the problem.
Dashboard stuck on "Connecting...", console dey show WebSocket connection ... failed. Reverse proxy no dey upgrade the WebSocket. Add 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 dey warn, or e dey warn wrongly. Either Ignore TLS/SSL Error dey ticked, wey disable cert checking, or monitor dey target IP and dey read wrong certificate because SNI dey missing, showing Hostname/IP does not match certificate's altnames. Untick ignore, then monitor by hostname.
SQLITE_BUSY or database disk image is malformed for the logs. /app/data volume dey for filesystem wey no get proper file locking, usually NFS; move am go local Docker volume and restore from backup.
FAQ
Where I suppose run my uptime monitor?
Run am for another server wey no be the ones e dey watch, preferably another provider or region. E go reach dem by hostname through public internet, just as your users dey do. If monitor share one box with the targets, the outage wey kill the server go kill the monitor too. Host wey overload fit make am report say services wey dey okay don go down. One small separate VPS go avoid both problems.
How I fit 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 confirm say the message arrive before you trust am.
Uptime Kuma fit monitor cron job or backup script?
Yes. Na the Push monitor be this. Uptime Kuma go give you URL, and you go curl am for the end of the script so e go fire only when the script succeed. If the job fail or the box dey down, the heartbeat no go ever arrive. You go receive alert after the interval pass. This na the only reliable way to know say scheduled job really run, because external check no fit see wetin dey happen inside am.
Uptime Kuma vs Zabbix, which one I suppose run?
Uptime Kuma answer the question, “e dey up from outside, and e alert me?” within ten minutes with almost no resource, plus status page. E no collect deep metrics like CPU, memory, and disk trends, or thresholds across the whole fleet. For that kind work, full Zabbix monitoring server na the heavier agent-based tool, and many people dey run both. You still dey decide wetin to run at all? our roundup of wetin to self-host for 2026 go show where monitoring fit inside the bigger picture.