Gatus
Health checks and a status page defined entirely in YAML, with no clickable state
Gatus is a single Go binary that reads a YAML file, runs HTTP, TCP, ICMP, DNS, SSH and TLS checks against the endpoints listed in it, and serves a dashboard and status page from the results. Everything it knows comes from the config file, so there is nothing to click and nothing to click back.
Our verdict on Gatus#
Gatus suits people whose infrastructure already lives in a git repo and who find clicking through a monitoring UI to add a check a step backwards. The whole 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. It reloads on write, no restart needed. The costs are real. Storage defaults to memory, so an unconfigured Gatus loses all history on restart. maximum-number-of-results defaults to 100, which at the default 60 second interval is roughly the last 100 minutes on the dashboard. And you cannot add a check from your phone at 2am. Current release is v5.36.0 under Apache-2.0. If those tradeoffs read as features, this is a better fit than Uptime Kuma.
Who Gatus is for#
Choose it if
- Someone who already manages their stack with compose files or Kubernetes manifests in git and wants checks reviewed the same way
- A team that wants a public status page whose contents cannot be edited by hand in production
- Anyone rebuilding their server regularly who wants monitoring restored by one file, not a database restore
- People who want to run identical monitoring in two places for cross-checking
Look elsewhere if
- You want to add and edit checks in a browser; that is the one thing it deliberately does not do
- You need months of latency history in the UI, which the per-endpoint result cap prevents
- You want system metrics or log collection; this only answers whether a check passed
What running it actually looks like#
One container, one file. ghcr.io/twin/gatus:stable or twinproduction/gatus:stable, port 8080, and a mount for config/config.yaml. Multi-arch images cover amd64, arm64 and armv7. It starts with no storage configuration at all, which is the trap: unless you set storage.type to sqlite and give it a path on a volume, everything is in memory. Editing the config while it runs triggers a live reload, and if the new file is invalid it logs a warning and keeps the old config, so a broken edit does not take the checks down. It does take you down on the next restart, because startup validation is strict. skip-invalid-config-update changes the running behavior only.
Resource profile#
- Memory
- 25 MB idle, around 60 MB in ordinary use. Static Go binary. With in-memory storage the footprint scales with endpoints times `maximum-number-of-results` (100 by default), which is small.
- CPU and acceleration
- Effectively zero between checks. `concurrency` defaults to 3, so with slow endpoints and long timeouts the limiter, not the CPU, is what makes checks drift.
- Storage growth
- Nothing grows unbounded: results are capped per endpoint (100 results, 50 events by default), so a SQLite file for a few dozen endpoints stays in the tens of MB.
- Operational load
- 1 of 5, Set and forget. One container, one file, no database service required. Config changes reload live, backup is copying a YAML file you probably already keep in git, and there is no schema to migrate.
Figures describe a small single-household install and are the working assumptions behind our stack planner. Your numbers will differ with library size, user count and hardware. See methodology.
Data and backup surface#
Back up config.yaml and you have backed up Gatus. It is the definition of every endpoint, alert, notification target and status page, and it belongs in git rather than in a backup job. The optional data.db (SQLite) holds only results, events and uptime history, all of which are recreated within minutes of restarting; losing it costs you the graph, not the monitoring. If you are on PostgreSQL, a nightly pg_dump is enough. Watch for secrets: use ${VAR} interpolation in the config so the file itself stays committable.
Derived backup shape
SQLite backup. sqlite3 <db> ".backup out.db" while running, or stop the container and copy. A live cp of a WAL-mode SQLite file can restore corrupt.
Traps and surprises#
Default storage is memory, and it is silent
Omit the storage block and Gatus runs perfectly, shows history, and forgets all of it on the next restart or container update. There is no warning banner. Set storage.type: sqlite with storage.path pointing at a mounted volume before you go to production, and check the file exists on disk after a restart rather than trusting the dashboard.
Only the last 100 results are kept per endpoint
maximum-number-of-results defaults to 100 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. Raise the values, raise the interval, or pull longer history from the uptime API endpoints, but do not expect a month of latency graphs out of the box.
An invalid config blocks the next start
While Gatus is running, a bad edit is tolerated: it logs that the update was not valid and keeps using the old configuration. That is easy to miss. The next time the container restarts, startup validation rejects the same file and the container crash loops. Read the log after every edit, or validate the file in CI before it reaches the server.
Concurrency is capped at 3 by default
concurrency limits how many endpoints are checked at once. With dozens of endpoints, or a handful with 10 second timeouts against something unreachable, checks queue behind each other and intervals stretch well past what you configured. Raise concurrency, or set it to 0 for unlimited, and keep per-endpoint client.timeout short so one dead host cannot stall the batch.
Alert thresholds count consecutive results
Alerts fire after a number of consecutive failures and resolve after a number of consecutive successes, both measured in checks rather than minutes. Changing an endpoint's interval silently changes how long an outage must last before you hear about it. If you move an endpoint from 60s to 5m checks, a threshold of 3 goes from three minutes of tolerance to fifteen.
Specifications#
- Category
- Monitoring and observability
- Licence
- Apache-2.0 (Permissive)
- Written in
- Go
- First release
- 2019
- Maturity
- Mature
- Datastore
- In-memory by default, SQLite or PostgreSQL optional
- Default ports
8080- Ships as
- docker, native binary, helm chart
- arm64 builds
- Yes
- Identity
- Native OIDC. Built-in OIDC under `security.oidc` with an issuer URL and an allowed-subjects list, plus basic auth as the simpler option.
- Replaces
- UptimeRobot, Pingdom, StatusCake, Better Stack Uptime
- Project site
- gatus.io
- Source
- github.com/TwiN/gatus
- Documentation
- github.com/TwiN/gatus#configuration
Alternatives to Gatus#
Everything else in monitoring and observability, closest in operational weight first.
| Tool | Ops load | Idle RAM | Licence |
|---|---|---|---|
| Beszel | 1, Set and forget | 40 MB | MIT |
| Dozzle | 1, Set and forget | 15 MB | MIT |
| Grafana | 2, Light | 150 MB | AGPL-3.0-only |
| Netdata | 2, Light | 150 MB | GPL-3.0-or-later (agent); dashboard UI under NCUL1 |
| Uptime Kuma | 2, Light | 120 MB | MIT |
| Prometheus | 3, Moderate | 150 MB | Apache-2.0 |
Where Gatus comes up elsewhere#
Head to head
Sources#
Facts on this page were checked against the project's own material. Where a number is our own estimate rather than a published figure, the text says so.
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.