Self-host service monitoring with Uptime Kuma
Monitor HTTP, TCP, DNS, and certificate expiry from a small VPS, with notifications and a public status page.

Host metrics tell you a server is alive. External checks tell you whether users can reach the service. Uptime Kuma provides HTTP, TCP, DNS, ping, certificate, and keyword checks through a straightforward web interface.
Run monitoring away from the systems it watches. If Kuma shares a VPS with your application, both disappear during the same outage.
Deploy with Compose
Create /opt/uptime-kuma/compose.yml:
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"
Start it:
cd /opt/uptime-kuma
sudo docker compose up -d
sudo docker compose logs --tail=50 uptime-kuma
Binding to loopback prevents direct public access. Put Caddy or Nginx in front with HTTPS.
Add a Caddy route
status.example.com {
reverse_proxy 127.0.0.1:3001
}
Reload Caddy, open the site, and create the administrator account. Use a unique password and enable two-factor authentication.
Design checks around user journeys
Do not monitor only the home page. Add checks for the things customers actually depend on:
GET /healthreturns 200 and a known keyword.- The API TCP port accepts connections.
- Authoritative DNS returns the expected address.
- The TLS certificate has more than 14 days remaining.
- A lightweight authenticated endpoint proves the database is reachable.
Use a 60-second interval for normal services. A five-second interval adds noise and makes transient network events look important. Require two or three failures before notifying.
Choose notifications deliberately
Configure at least two paths, such as email plus a phone notification service. Send a test notification and record which team or person owns the response.
Group related monitors into a notification policy. A database health check and the site depending on it should not wake two different people with no context.
Publish a status page
Create a status page with customer-facing services rather than internal hostnames. Group them as API, dashboard, network, and billing. Avoid exposing management endpoints or infrastructure IPs.
Write incident updates for users:
Investigating elevated API error rates in the London region.
Existing sessions remain connected. New deployments may be delayed.
That is more useful than “server-4 is down.”
Back up Kuma
The data directory contains configuration and history. Stop the container briefly or use a filesystem snapshot before copying it off-site:
cd /opt/uptime-kuma
sudo docker compose stop
sudo tar -czf /var/backups/uptime-kuma-$(date +%F).tgz data
sudo docker compose start
Test monitoring by blocking a test endpoint, confirming an alert arrives, then restoring it. Monitoring that has never detected a controlled failure is still an assumption.
Add a monitor for the monitor
Create a simple check from a second system against https://status.example.com. This can be a hosted check, a second Kuma instance, or a lightweight external probe. It should alert when the primary Kuma dashboard is unavailable, not only when an application behind it fails.
For HTTP checks, set an expected status code and, where possible, a response keyword. A proxy can return a successful error page with status 200; a keyword check catches that failure. For TCP checks, test the actual protocol with a synthetic transaction when supported.
Upgrade without losing history
Back up data/, pull a reviewed image tag, and recreate the container:
cd /opt/uptime-kuma
sudo docker compose stop
sudo tar -czf /var/backups/uptime-kuma-before-upgrade.tgz data
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail=100 uptime-kuma
Open the dashboard, verify monitors and notifications, and keep the backup until the new version has run through a normal check interval.
Continue reading