Tech Digest

Head to head

Uptime Kuma vs Gatus

Both answer the same question for free on hardware you own. They disagree about where the answer's configuration should live, and that disagreement decides which one you should run.

Last reviewed 2 tools compared

Uptime Kuma or Gatus?

Run Uptime Kuma unless your infrastructure is already defined in git, in which case run Gatus. Uptime Kuma gives you around 90 notification providers, certificate and domain expiry warnings, and a status page you configure in a browser, at about 120 MB of RAM. Gatus is a 25 MB Go binary whose entire state is a YAML file, which means monitoring you can code-review and restore with git clone, at the cost of never being able to add a check from your phone.

Both tools poll endpoints on a schedule and shout when one fails. Both are free, both run in one container, both replace an UptimeRobot free tier that caps you at five minute intervals. The interesting difference is where the truth about your monitoring lives: in a SQLite database that a web UI writes to, or in a file you commit next to the compose file it monitors.

That sounds like a taste question. It is not. It determines how you rebuild after a disk failure, whether a second person can review a change, and whether you can add a check at 2am from a phone.

What the specification table decides#

Two rows carry the argument. Idle memory is 120 MB against 25 MB, which sounds decisive and mostly is not, because on any machine big enough to run the services you are monitoring, 95 MB is noise. Datastore is the row that matters: SQLite by default for Uptime Kuma, in-memory by default for Gatus. That second default is a loaded gun, and it is covered below.

SpecificationUptime KumaGatus
LicenceMIT (Permissive)Apache-2.0 (Permissive)
Written inJavaScript / Node.jsGo
First release20212019
MaturityMatureMature
DatastoreSQLite by default, MariaDB optional in v2In-memory by default, SQLite or PostgreSQL optional
Services to run11
Idle memory120 MB25 MB
Memory in use250 MB60 MB
Operational load2 / 5, Light1 / 5, Set and forget
IdentityLocal accounts onlyNative OIDC
arm64 buildsYesYes
Default ports30018080
Backup shapeSQLite backupSQLite backup

Config in a database versus config in a file#

Uptime Kuma's monitors, notification credentials, status pages and maintenance windows all live in kuma.db. There is no export to a text format you would want to diff. Backing up your monitoring means backing up a database, and reproducing it on a new machine means restoring that database, not re-running a script.

Gatus is the opposite position taken to its conclusion. The application is config/config.yaml. Point GATUS_CONFIG_PATH at a directory and it deep merges every .yaml in it, so each service can ship its own check file next to its compose file:

yaml
endpoints:
  - name: immich
    group: media
    url: "https://photos.example.org/api/server/ping"
    interval: 60s
    conditions:
      - "[STATUS] == 200"
      - "[RESPONSE_TIME] < 800"
    alerts:
      - type: ntfy
        failure-threshold: 3
        success-threshold: 2
        send-on-resolved: true

Save the file and Gatus reloads live. No restart, no click, and the change went through the same review as the deploy. If the machine dies, git clone plus docker compose up restores every check exactly.

The cost is equally concrete: you cannot add a check without an editor and a shell, and you cannot silence a noisy monitor from your phone during an outage. If that sounds fine, stop reading and run Gatus. Most people find out at 2am that it is not fine.

Uptime Kuma v2 fixed the thing that used to kill it#

v1 wrote one heartbeat row per check, forever. Fifty monitors at 60 second intervals is roughly 72,000 rows a day. People arrived at multi-gigabyte kuma.db files, slow dashboards, and a container that took a minute to start.

v2 changed the storage model: 24 hours of raw heartbeats per monitor, then minute, hour and day aggregate tables, with a 365 day default retention. A 50 monitor instance now settles in the low hundreds of MB and stays there. Version 2 is out of beta and current at 2.5.3 (August 2026), published as the :2 tag.

Two things to know before you upgrade:

  • The migration is one way and must not be interrupted. On first start v2 aggregates your entire v1 heartbeat table. The wiki reports about 7 minutes for 20 monitors and 90 days of data; on a Pi with two years of history it runs for hours. An interrupted migration means restoring from backup. Snapshot /app/data and pin the v1 tag first.
  • Deleting rows does not shrink the file. Retention deletes rows, SQLite keeps the pages on its own free list. Use Shrink Database in Settings, which runs VACUUM and needs free space roughly equal to the current database size.

MariaDB is supported in v2, but the project explicitly does not support converting an existing SQLite install, and warns that third party converters miss indexes. Treat MariaDB as a new install decision, and for anything under a few hundred monitors do not bother.

Gatus's defaults will silently lose your history#

This is the uncomfortable part that the project's own README states plainly and that almost every blog post about Gatus omits. Omit the storage block and Gatus runs, shows a dashboard, draws graphs, and forgets all of it the next time the container restarts. There is no warning banner.

yaml
storage:
  type: sqlite
  path: /data/data.db

Set that, mount /data, restart once, and confirm the file exists rather than trusting the dashboard. Then note the second cap: maximum-number-of-results defaults to 100 per endpoint and maximum-number-of-events to 50, regardless of storage backend. At the default 60 second interval your dashboard shows roughly the last hour and a half of checks. Raise the values or lengthen the interval, but do not expect a month of latency graphs.

Third default worth changing: concurrency is 3. With a few dozen endpoints, or a handful of unreachable ones with 10 second timeouts, checks queue behind each other and your real interval drifts well past what you configured.

The failure mode nobody mentions: an invalid config#

Gatus tolerates a bad edit while running. It logs that the update was not valid and keeps the old config, which is good behavior and also easy to miss. The next time that container restarts, startup validation rejects the same file and you get a crash loop, usually at the worst moment because container restarts happen during host reboots and update windows. Validate in CI, or at minimum read the log after every edit.

Uptime Kuma's equivalent trap is the reverse shape: it is too easy to change something and forget. There is no history of who edited what, and no diff. If two people administer it, that matters.

Notifications and status pages#

Uptime Kuma wins on breadth without much argument: around 90 built-in providers plus Apprise bundled in the image, per-monitor notification assignment, and certificate expiry and domain expiry warnings that Gatus does not offer as first-class features. Public status pages are configured in the UI and can be mapped to your own domain.

Gatus has fewer providers but the same essentials (ntfy, Gotify, Discord, Telegram, Slack, email, generic webhook) and one advantage: because the status page is generated from config, nobody can hand-edit production. For a team that is a feature. See ntfy vs Gotify if you have not picked a notification receiver yet.

Gatus also has real OIDC, configured under security.oidc with an issuer URL and an allowed-subjects list. Uptime Kuma has local accounts with optional TOTP and nothing else, so putting it behind Authelia or another forward-auth portal is the only way to get single sign-on. That is covered in Single sign-on for self-hosters.

Neither one can tell you it went down#

A single instance monitoring your whole stack cannot report its own outage, and both projects have no clustering story. This is the most common gap in a self-hosted monitoring setup and the cheapest one to close: run a second checker somewhere else. A 25 MB Gatus on a 2 euro VPS checking five public URLs is the standard answer, and it happens to be the configuration Gatus is best at. Alternatively use a push heartbeat: Uptime Kuma's push monitor type gives you a URL that your cron job must hit, so silence becomes an alert.

Which one for your situation#

Your situationUseWhy
10 to 100 home services, one operator, no git workflowUptime KumaUI, 90 notification providers, cert expiry, done in an evening
Stack already in git, deploys reviewedGatusChecks ship with the service they check and reload live
Public status page for a small businessUptime KumaStatus pages, custom domain and incident messages without an editor
Second, external vantage point on a tiny VPSGatus25 MB, one file, no database to babysit
You want SSO on the monitoring itselfGatusNative OIDC; Uptime Kuma needs an auth proxy
You rebuild your server oftenGatusRestore is git clone, not a database restore
You want to silence an alert from your phoneUptime KumaGatus deliberately has no clickable state
You need CPU, disk and memory, not up or downNeitherAdd Beszel or Prometheus alongside

What to do next#

Decide by asking one question honestly: is your infrastructure already in a git repository that you actually commit to? If yes, Gatus, and set storage.type: sqlite in the first five minutes. If no, Uptime Kuma, and back up /app/data with a stopped container or sqlite3 kuma.db ".backup out.db" rather than a live cp, because a WAL-mode file copied live can restore corrupt.

Either way, uptime checks are only half of a monitoring setup. Read The minimum viable monitoring stack for the rest, then add a metrics layer: Beszel vs Netdata covers the small end and Netdata vs Prometheus and Grafana covers the case where you want to query history. Dozzle handles the third leg, which is reading logs when something is actually broken. The Monitoring category has the full set.

Questions#

Is Uptime Kuma v2 stable yet?

Yes. Version 2 is out of beta and is what the project publishes as the louislam/uptime-kuma:2 tag, with 2.5.3 shipping in August 2026. The upgrade from v1 is the part to plan: on first start it aggregates your entire heartbeat table, which the migration wiki says took about 7 minutes for 20 monitors and 90 days of data and can run for hours on slower hardware. It is one way. Snapshot /app/data and pin the old tag before you pull.

How big does the Uptime Kuma database get?

In v1 it grew without limit, because every check wrote one heartbeat row forever: 50 monitors at 60 second intervals is roughly 72,000 rows a day, and multi-gigabyte SQLite files were normal. v2 keeps only 24 hours of raw heartbeats per monitor and rolls the rest into minute, hour and day aggregates with a 365 day default retention, so a 50 monitor instance settles in the low hundreds of MB. Deleting rows does not shrink the file; use Shrink Database in Settings, which runs VACUUM.

Why does Gatus lose all my history when it restarts?

Because storage.type defaults to memory and nothing warns you. An unconfigured Gatus looks completely healthy, shows graphs, and forgets everything on the next container update. Set storage.type: sqlite with storage.path on a mounted volume before you rely on it, then restart once and confirm the file exists on disk. Even then, maximum-number-of-results caps history at 100 results per endpoint by default, which at 60 second checks is under two hours.

Can Gatus send alerts to Telegram, ntfy or Discord?

Yes, through alerting providers configured in the same YAML file, and thresholds are set per endpoint as a number of consecutive failures. The trap is that thresholds count checks, not minutes: moving an endpoint from a 60 second to a 5 minute interval turns a failure threshold of 3 from three minutes of tolerance into fifteen. Uptime Kuma's advantage here is breadth, with around 90 built-in providers plus Apprise.

Can I run both?

Yes, and it is a reasonable answer for anything that matters. Run Gatus on a cheap VPS checking your public endpoints from outside, and Uptime Kuma at home checking internal services. Neither tool can tell you that it went down itself, so a single instance monitoring everything has an obvious blind spot. Two vantage points on different networks costs a few euros a month and removes it.

Does either one collect CPU, memory or disk metrics?

No. Both answer whether a check passed and nothing else. If you want to know why a service went down, or catch a disk filling before it does, you need a metrics tool alongside them: Beszel for a small dashboard with threshold alerts, or Prometheus if you want to query history.

Sources#

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.