If you run more than three services, you need uptime monitoring that pings them every 30 seconds and tells you the second one of them disappears. SaaS tools like UptimeRobot (free tier) and Better Stack (paid) work, but at 20+ monitors and multi-region checks, the bill climbs fast and your probe data sits on someone else's servers.
This guide compares the four most credible self-hosted uptime monitoring stacks you can run on a $5 VPS in 2026: Uptime Kuma, Gatus, Statping-ng, and a self-hosted Better Stack (open-core clone). I have run all four in production at various points; this is the comparison I wish I had when migrating off Pingdom three years ago.
You can also browse the full Uptime Monitoring category on the SelfHostStack directory for a wider tool list.
Quick comparison table
| Tool | Stack | Probe types | Status page | Multi-region | Best for |
|---|---|---|---|---|---|
| Uptime Kuma | Node 20 / SQLite | HTTP, TCP, ICMP, DNS, push, MQTT, gRPC, Docker | Built-in (subdomains, custom domain) | Via external probes | Solo devs, homelabs, SMBs up to 100 monitors |
| Gatus | Go / SQLite/Postgres | HTTP, TCP, ICMP, DNS, TLS, WebSocket, SSH | Built-in (config-as-code) | Via external instances | DevOps teams that want config-as-code and Prometheus-style health |
| Statping-ng | Go / SQLite | HTTP, TCP, ICMP, DNS | Built-in (themes) | Manual via second instance | Legacy users; project effectively maintenance mode since 2024 |
| Better Stack self-hosted | Rails / Postgres | HTTP, TCP, ICMP, DNS, SSL | Built-in (incident timeline) | Yes (incidents dashboard) | Teams wanting an UptimeRobot-like UI with incident management |
The short version: Uptime Kuma wins on UX and ecosystem, Gatus wins on configuration discipline and resource footprint, Statping-ng is a dead end, and self-hosted Better Stack is only worth it if you actually need its incident management module.
Uptime Kuma (the default choice for most people)
Uptime Kuma is a Node.js application that runs comfortably in a 256MB Docker container. Setup is one docker-compose.yml plus a SQLite volume. The UI is what makes it special: drag-and-drop monitor creation, certificate expiry warnings, status pages with custom domains (powered by a built-in Caddy server on a separate port), and integrations for 90+ notification targets (Telegram, Discord, Slack, PagerDuty, Pushover, Webhook, SMTP).
A minimal production deploy:
version: "3.8"
services:
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: uptime-kuma
restart: unless-stopped
volumes:
- ./data:/app/data
# Optional: host network so ICMP ping works
network_mode: host
environment:
- UPTIME_KUMA_PORT=3001
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/"]
interval: 30s
timeout: 5s
retries: 3
Pros
- Best-in-class UX. Non-technical team members can add a monitor without reading docs.
- Rich probe types: HTTP(S), TCP, ICMP ping, DNS, MQTT, gRPC, Docker container, push (inbound), Steam game server.
- Status pages support multiple sub-pages, custom CSS, and password-protected sections.
- Active development — 2.x line adds real-time WebSocket, multi-tenancy, and 2FA.
Cons
- SQLite default. Past ~5,000 monitors or 50 status pages you should migrate to the built-in Postgres backend.
- Each external probe runs in the same process; if your Kuma instance dies, every probe dies with it. Multi-region requires deploying 2-3 instances and using a separate aggregator.
- No native incident management. When something flaps, you get a flood of notifications; pairing it with ntfy or Alertmanager is recommended.
When NOT to use it: if you need config-as-code (everything in YAML committed to git) or you want <10MB RAM usage per probe. Gatus wins on both.
Gatus (the DevOps-friendly choice)
Gatus is a Go binary, ~12MB RAM at idle, and configured entirely through a config.yaml you commit to git. The mental model is closer to Prometheus Blackbox exporter than to a SaaS dashboard: each endpoint is a "service" with an ordered list of "conditions" (status code == 200, response time < 500ms, TLS cert expiry > 14 days, body contains <title>). Failed conditions trigger alerts.
services:
- name: selfhoststack-directory
url: https://selfhoststack-8z4.pages.dev/healthz
interval: 60s
conditions:
- "[STATUS] == 200"
- "[RESPONSE_TIME] < 800"
- "[CERTIFICATE_EXPIRATION] > 168h"
alerts:
- type: slack
webhook-url: "https://hooks.slack.com/services/XXX"
send-on-resolved: true
- name: postgres-primary
url: "tcp://db.internal:5432"
interval: 30s
conditions:
- "[CONNECTED] == true"
Pros
- Config-as-code is the killer feature. Monitor definitions are reviewable, diffable, and reproducible across staging/prod.
- Extremely lightweight (Go, no Node runtime, no JS heap).
- Native multi-region via Gatus itself — run a second instance in EU, point both at the same Postgres, and you get distributed probes for free.
- OpenTelemetry support for shipping probe data to Grafana/Tempo.
Cons
- UI is functional but ugly. Your CTO will not be impressed.
- Adding a monitor requires editing YAML and reloading the container; no admin UI for non-engineers.
- Smaller community than Kuma; ~80 contributors vs 700+.
When NOT to use it: if you need a public status page that customers will see, or if your team is non-technical.
Statping-ng (avoid for new deployments)
Statping-ng was a credible Pingdom alternative from 2020-2022. As of 2026 the original maintainer has stepped back, releases are sporadic, and the Go version has a known issue with HTTP/2 probes that surfaces on sites behind Cloudflare. The UI also feels frozen in 2021. I keep one instance for historical reasons, but every new deploy goes to Uptime Kuma or Gatus.
The one case where Statping-ng still makes sense: you need a single-binary, no-Docker monitor with native Windows support. Neither Kuma nor Gatus ships Windows binaries as cleanly.
Self-hosted Better Stack (incident management matters)
The official Better Stack is closed-source SaaS. The "self-hosted" version referenced in the community is usually Statping-ng's bigger cousin — a Rails app called Better Uptime (community fork) or, more commonly, a stack of Uptime Kuma + Incident.io clone (Sleek) + Statuspal. None of these are turnkey.
If you actually need the UptimeRobot-style experience (incident timeline, post-mortem templates, on-call schedules), the realistic self-hosted path is:
- Uptime Kuma for the probes and status page.
- Grafana Incident (open source, Grafana Labs) for incident management.
- Keep or Sleek for on-call schedules and alert routing.
The result: more moving parts, but you get the same workflow as Better Stack at $0/mo and your probe data never leaves your VPS.
Recommendation matrix
| Scenario | Pick | Why |
|---|---|---|
| Solo dev with < 20 monitors | Uptime Kuma | Fastest setup, best UX |
| DevOps team, 20-200 monitors, multi-region | Gatus + Uptime Kuma public status page | Gatus for internal probes, Kuma for the customer-facing status page |
| 5-person startup with on-call rotation | Uptime Kuma + Grafana Incident + Keep | Cheap, integrated |
| 100+ services, regulated industry, need audit trail | Better Stack hosted (or self-hosted with Postgres + audit logs in Kuma) | Compliance pressure beats cost savings |
| Family homelab, just want a Telegram alert when Plex dies | Uptime Kuma | Lowest friction |
Backup and operational notes
Treat your uptime monitor like any other critical service:
-
Backup the SQLite database nightly. Uptime Kuma stores everything in
data/kuma.db; a 5GB database with 3 years of uptime data compresses to 80MB.rclone syncto Backblaze B2 is enough. - Monitor the monitor. Uptime Kuma cannot alert you if it is itself down. Deploy a second tiny Kuma instance on a different VPS (or use a free UptimeRobot account) to ping your primary instance. Yes, this is the classic "who watches the watchmen" loop.
-
Pin the version. Both Kuma and Gatus have shipped breaking 1.x → 2.x migrations. Use
louislam/uptime-kuma:1or pin the major in your compose file; never use:latestin production. -
Separate status page domain. Put the status page on a subdomain (
status.yourdomain.com) so a DNS outage does not take down both your service and the page that reports on it. - TLS cert monitoring. Both tools support certificate expiry checks; configure the threshold to 14 days and forward to a separate alert channel so certificate renewal bugs do not get buried in uptime noise.
Closing thought
The "right" uptime monitor in 2026 is whichever one your team will actually configure and look at. Uptime Kuma is the default for a reason — it has the lowest barrier between install and first useful alert. If your team is comfortable with YAML and a CLI, Gatus pays for itself in lower RAM and config-as-code. Both are excellent; both are free; both are MIT/AGPL. The only wrong choice in 2026 is paying Pingdom $20/mo per monitor when a $5 VPS and 30 minutes of docker-compose gives you the same answer.
For deeper dives into specific corners of the self-hosted monitoring stack (Prometheus, Grafana, Loki, Alertmanager), see the Self-Hosted Monitoring Stack guide and the Status Pages directory on SelfHostStack.
Top comments (0)