Tech Digest

Category

Monitoring and observability

Two rules decide whether monitoring helps or just adds another service to maintain: it must not run on the machine it watches, and it must cost less than that machine's actual workload.

8 tools profiledMedian idle memory 120 MBLast reviewed

Monitoring goes wrong in two structural ways, and neither is about picking the wrong tool.

The first is that it runs on the machine it watches. Fine until the day it matters, when the host wedges, the checker goes with it, and the absence of an alert reads exactly like everything being fine. A monitor on the box that died tells you nothing.

The second is that it costs more than the thing it watches. Per-second metrics with anomaly detection on a 2 GB Pi running three containers makes the observability stack the primary workload: a new thing that can break, watching two old things that mostly did not.

The default: Uptime Kuma, on a different machine#

Uptime Kuma is the default for "is my stuff up" if you want a UI rather than a config file. One container, no database service, 120 MB idle, notifications to almost anything, and a status page for the household. Version 2 changed the storage model: v1 wrote one heartbeat row per check forever, v2 keeps 24 hours of raw beats and rolls the rest into minute, hour and day tables.

Run it somewhere else: a Pi, an old thin client, the cheapest VPS you can find. The instance watching your server must not depend on your server.

The default breaks down in two places.

Your infrastructure lives in git and you want checks reviewed like code. Gatus, 25 MB, ops load 1, entirely YAML. Two defaults bite: storage defaults to memory, so an unconfigured Gatus shows history and forgets it on restart with no warning, and maximum-number-of-results defaults to 100, which at a 60 second interval is the last hour and a half.

You want resource numbers across several machines, not just up or down. Beszel: a 40 MB hub, an agent image around 4 MB, CPU, memory, disk, containers, ZFS, SMART and alerts. The 30 day retention windows are hard-coded rather than configurable, so seasonal comparisons need something else.

Alert on what you would get out of bed for#

Write the alert list before you install anything. For a home server it is four things: the host is unreachable, a disk is failing or nearly full, backups have not run in 48 hours, and a certificate expires within a week. That is what should reach your phone.

Everything else is a graph. CPU pinned during a library scan is not an incident, and memory at 85 percent is how Linux is supposed to behave. Alerting on those produces a channel you learn to ignore, which is worse than no channel because you believe you are covered.

When you actually want the heavy tier#

Netdata shows you more about a machine ten seconds after installation than anything else here, at about 150 MiB of RAM, 5 percent of a core and 4 GiB of disk. Fair on a server, bad on an SD card. The agent is GPLv3, but the dashboard UI is NCUL1 and pulled from a CDN.

Prometheus plus Grafana is for asking questions of the data rather than looking at it. Budget the discipline: 60 second scrapes, the TSDB on an SSD, retention set explicitly, and a check on prometheus_tsdb_head_series after every new exporter, because cardinality turns a 150 MB process into a multi-gigabyte one.

For logs, Dozzle is 15 MB and retains nothing by design. Its real lesson is that Docker's json-file driver has no size limit: set max-size and max-file in /etc/docker/daemon.json before a chatty container fills the root disk.

The mistakes people actually make#

  • Putting Uptime Kuma's SQLite on the NAS. The README lists NFS as unsupported and CIFS behaves the same: database is locked, then a database that will not open. Keep /app/data local.
  • Exposing port 19999. Netdata's dashboard is unauthenticated by design. Bind it to a private interface in netdata.conf and put an authenticating proxy in front.
  • Leaving Prometheus retention unset. It keeps 15 days and deletes the rest silently, which you discover when the graph you wanted stops dead.
  • Bind-mounting a Grafana directory as root. The image runs as uid 472, so -v ./grafana:/var/lib/grafana fails with permission denied and exits. chown -R 472:472, or use a named volume.
  • Pointing a Beszel agent at localhost. With hub and agent as containers on one host, localhost resolves inside each namespace and the connection silently fails; the documented fix is a shared unix socket. Uptime Kuma behind a proxy that does not pass websocket upgrade headers fails the same quiet way: the page loads and then spins forever.

The minimum viable monitoring stack is the build sheet. For the tool decisions, Uptime Kuma vs Gatus, Beszel vs Netdata and Netdata vs Prometheus and Grafana. The alerts that matter most are about data, so pair this with Backups that actually restore and ZFS, btrfs, mdadm or one disk, then score yourself with Resilience scorecard. For delivery, ntfy vs Gotify.

Every monitoring tool we profile#

Sorted by operational load, lightest commitment first. Every row links to a full profile with the resource numbers, the upgrade traps and the restore path.

ToolOps loadIdle RAMLicenceIdentityBackup shape
Beszel1, Set and forget40 MBMITNative OIDCSQLite backup
Dozzle1, Set and forget15 MBMITTrusted proxy headerFile copy
Gatus1, Set and forget25 MBApache-2.0Native OIDCSQLite backup
Grafana2, Light150 MBAGPL-3.0-onlyNative OIDCSQLite backup
Netdata2, Light150 MBGPL-3.0-or-later (agent); dashboard UI under NCUL1Auth proxy onlySQLite backup
Uptime Kuma2, Light120 MBMITLocal accounts onlySQLite backup
Prometheus3, Moderate150 MBApache-2.0Auth proxy onlyMixed
Scrutiny3, Moderate100 MBMITAuth proxy onlySQLite backup

Profiles#

Head to head in this category#

Guides for this category#

Questions#

Where should the monitoring run?

Somewhere that can fail independently of what it watches. A checker on the same host reports perfectly right up to the moment the host dies, and then reports nothing, which is indistinguishable from silence. The cheapest correct answer for a homelab is a second small machine (a Pi, an old thin client, a low-cost VPS) running the uptime checks and holding the alert channel. Uptime Kuma is honest about this limit: one instance is one vantage point and it cannot tell you it went down itself. Pair it with an external ping or a second instance elsewhere.

What should actually send me an alert?

Only things you would get out of bed for, which for a home server is a very short list: the host is unreachable, a disk is failing or full, backups have not completed in 48 hours, and certificates expire within a week. Everything else is a graph you look at when something feels wrong. CPU at 90 percent during a media scan is normal; a paging rule for it teaches you to ignore the channel, and an ignored channel is worse than no channel because you believe you are covered.

How much RAM should monitoring use?

Less than the service it protects. Beszel's hub idles around 40 MB and its agent image is about 4 MB. Gatus is 25 MB, Dozzle 15 MB, Uptime Kuma 120 MB. Netdata publishes about 150 MiB of RAM, roughly 5 percent of a core and about 4 GiB of disk by default, which on a 2 GB Raspberry Pi watching three containers means the monitoring is the workload. Prometheus plus Grafana is 300 MB before you count exporters. Pick the tier that matches the machine, not the tier that matches the dashboard screenshots.

Uptime Kuma or Gatus?

Uptime Kuma if you want to add a check from your phone at 2am and see a status page without editing YAML. Gatus if your infrastructure already lives in a git repository and clicking through a UI to add a check feels like a step backwards: point GATUS_CONFIG_PATH at a directory and it deep merges every .yaml in it, so each service ships its check next to its compose file, reloaded on write. Gatus costs 25 MB against Uptime Kuma's 120 MB. Both do the job; this is a workflow preference, not a capability gap.

Do I need Prometheus and Grafana?

Only if you will actually query the data. Prometheus is a component, not a product: it stores numbers on a timeline, has no dashboards worth showing anyone, and defaults to 15 days of retention when neither storage.tsdb.retention.time nor .size is set. Grafana on its own is an empty shell with nothing to graph. Together they are the right answer when you need PromQL, alert rules over expressions, or history past a month. For "is it up and is the disk full", they are several hundred megabytes of infrastructure answering a question a 25 MB binary answers.

Will Prometheus wear out my SD card?

Yes, if you leave it on the boot media with default settings. The example config scrapes every 15 seconds, every scrape appends to a write-ahead log, the head block is written out every two hours and compaction rewrites blocks after that. That constant small-write pattern is exactly what kills SD cards and cheap USB drives, typically four to twelve months in. Set the global scrape interval to 60s for host metrics and put the TSDB on an SSD or NVMe. Storage itself is cheap: 1,000 active series at 15 second scrapes is roughly 10 MB a day compacted.

Do I need disk health monitoring?

You need to know a drive is dying before it does, but not necessarily with a dashboard. On a two-disk mini PC, smartd with an email alert does most of the job for nothing. Scrutiny earns its 100 MB and its InfluxDB dependency on a multi-bay NAS where trends across many drives matter. If you run it, note that NVMe needs --cap-add SYS_ADMIN on top of the usual SYS_RAWIO, and that leaving the collector on its daily default is deliberate: setting it to every 15 minutes produces nearly 55,000 datapoints per device.

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