Beszel: Lightweight Server Monitoring Without the Grafana Overhead
A tiny hub-and-agent dashboard for when full Prometheus is overkill

Contents
I will defend the Grafana-and-Prometheus stack to anyone who’ll listen. But I have to be honest about something: it is a lot of machinery to answer the question “is my little VPS okay?” For a serious homelab with a dozen nodes and real alerting needs, the weight is justified. For three boxes and a Raspberry Pi, standing up Prometheus, exporters, Grafana, and a pile of YAML to find out whether a disk is filling up feels like hiring a structural engineer to hang a picture.
Beszel is what you reach for when you’ve had that exact thought. It’s a young project — it only really appeared on people’s radars over the last year or so — and it does deliberately less than Prometheus, which turns out to be exactly the point.
I should be clear about where the line sits, because I am not arguing against the big stack. I run Grafana and Prometheus where it belongs, and for anything with serious alerting needs, custom application metrics, or more than a handful of nodes, it is the correct answer and Beszel is not. Beszel is for the tier below that — the boxes that are real enough to want eyes on but not important enough to justify a metrics platform. Most homelabs have more machines in that tier than in the serious one, which is exactly why a tool aimed at it is so welcome.
The hub-and-agent shape
Beszel has two parts and that’s the whole story. There’s a hub, which is a single small binary that serves the web dashboard and stores history in an embedded database (it’s built on PocketBase, so there’s no separate Postgres to babysit). And there’s an agent, an even smaller binary you run on each machine you want to watch. The agent collects CPU, memory, disk, network and Docker container stats, and the hub pulls them in over an SSH-based connection.
That SSH detail matters. There’s no broad network of scrape targets to firewall, no exporters listening on ports you have to remember to secure. The hub holds a key, the agent trusts it, and the connection is encrypted by design. For watching a VPS that lives out on the public internet, this is a far less nervous arrangement than poking a metrics port through a firewall.
Standing it up
The hub is one container. Here’s the gist:
| |
Open the web UI, create your admin account, and the dashboard walks you through adding a system. It hands you the agent command and the public key to drop in. On each machine you want monitored, the agent runs as its own little container or a systemd service:
| |
Mount the Docker socket read-only and the agent will break out per-container CPU and memory too, which is genuinely useful — you get a per-service view without running cAdvisor and the small mountain of metrics it produces.
If you would rather run the agent as a systemd service than a container — which I prefer on hosts that are not otherwise running Docker, to avoid pulling in the whole engine just for monitoring — the install script drops in a unit you manage the usual way:
| |
The agent is a single static binary, so the systemd route adds essentially nothing to the host. On a Pi or a minimal VPS where every megabyte of RAM is accounted for, that is the tidier option. The same instinct that makes running containers without the full Docker daemon appealing applies here: don’t drag in a heavyweight dependency for a job a small binary already does.
Connecting the hub to the agents
The SSH key exchange is the one bit that trips people up, so it is worth understanding rather than copy-pasting blindly. The hub generates a key pair; the agent is told the hub’s public key via the KEY environment variable. When the hub connects, the agent checks that the connection is signed by a key it trusts. There is no password, no shared secret in transit, and nothing for an attacker scanning the agent’s port to do — without the matching private key, the agent simply refuses to talk.
This is why I am comfortable putting the agent on a public-facing VPS in a way I would never be comfortable exposing a raw Prometheus exporter. The agent listens, but it answers only to the one hub that holds the key. If you run the hub itself behind a reverse proxy, you can also put the web UI behind your SSO so the dashboard is not open to the world either — the same front-door pattern that suits the rest of a hardened homelab.
What you get, and what you don’t
The dashboard is clean and immediate: a list of your systems with status dots, and click-through detail pages with the graphs you actually look at — load, memory pressure, disk usage and I/O, network throughput, container breakdowns. It keeps a rolling history so you can see trends over days and weeks, not just the current instant.
A small detail I appreciate: the dashboard is genuinely fast and genuinely legible on a phone. Most monitoring tools treat mobile as an afterthought, and you end up pinch-zooming around a desktop Grafana panel trying to read a number. Beszel’s status-dot list collapses sensibly on a small screen, so the “is everything okay” glance — which is the question you ask ninety percent of the time — takes one tap from a bookmark. For a tool whose whole reason to exist is reducing friction, getting that right matters more than another twelve metrics nobody checks.
Beszel also does the thing that makes monitoring worthwhile: alerts. You can set per-system thresholds — CPU over X for Y minutes, disk above a percentage, a system going offline — and have them delivered. It leans on the Shoutrrr notification library under the hood, so it can fan out to email, Telegram, Discord, ntfy and a long list of other targets without you wiring up Alertmanager.
What you don’t get is the open-ended power of the real stack. There’s no PromQL, no custom dashboards full of bespoke panels, no thousand community dashboards to import, no arbitrary exporters for your UPS or your solar inverter or your weird IoT thing. Beszel watches the host and its containers, presents that beautifully, and stops there. If you want to graph an application-specific metric you invented, this is the wrong tool.
Honesty about its youth
This is a new project, and I’d be doing you a disservice to pretend otherwise. New projects move fast and occasionally break things; the feature set is still growing; and the bus factor on a young open-source tool is always worth a thought before you make it load-bearing. It’s MIT-licensed and the development has been brisk and responsive, which are good signs, but it hasn’t yet earned the boring, decade-deep stability that Prometheus has. Treat it accordingly: brilliant for keeping an eye on things, not yet where I’d hang a pager rotation that someone’s job depends on.
Troubleshooting: when an agent won’t show up
The failures are few and nearly always the same handful.
The agent shows “down” right after adding it. Almost always a connectivity or key problem. Check that the hub can actually reach the agent’s port — a firewall on the agent host, or a cloud provider security group, is the usual culprit. Then confirm the KEY value on the agent exactly matches the public key the hub displayed; a truncated paste or a stray newline breaks the handshake silently. journalctl -u beszel-agent -f on the agent side will show the rejected connection.
No container stats appear. The agent can read the host fine but the per-container breakdown is empty. This means the Docker socket isn’t mounted, or it’s mounted but the agent user can’t read it. Confirm the :ro socket mount is present and that the agent process has permission — running the agent container as root, or adding it to the docker group for the systemd install, resolves it.
Stats stop after a reboot. A container agent without a restart policy won’t come back. Add restart: unless-stopped to the agent’s compose service; for the systemd install, run systemctl enable beszel-agent so it survives reboots.
The hub forgets everything. If history vanishes after a redeploy, you didn’t persist the data volume. The hub keeps its embedded database under /beszel_data — that volume must be a real bind mount or named volume, not ephemeral, or every docker compose down wipes your history. Back that directory up the way you back up anything else stateful.
How it compares to just tailing logs
The honest alternative to Beszel for a tiny setup isn’t Prometheus — it’s nothing, or a cron job that emails you when a disk is full. I lived on exactly that for years: a couple of shell scripts and the vague hope that I’d notice. The trouble with the nothing-approach is that it only ever tells you about the failure mode you anticipated. A script that checks disk space says nothing when memory pressure starts swapping a box into the ground, or when a container is quietly restarting in a loop. A dashboard that shows you trends — memory creeping up over a week, disk filling at a steady rate, a container’s CPU climbing — lets you act before the threshold rather than after the outage. That shift from reactive to slightly-ahead is the actual value, and it is why even on a media box like a Jellyfin server it earns its keep: you spot the drive filling with new library additions before playback starts failing, not after.
The verdict
Beszel is for the person who wants a tidy single-pane view of a few machines and could not be bothered to operate a metrics platform to get it. If that’s you — a handful of VPSes, a home server, a Pi or two — it’s a delight: minutes to set up, light enough to forget it’s running, pretty enough that you’ll actually open it. I added it to a couple of small boxes that genuinely didn’t warrant a Prometheus install, and within ten minutes I had exactly the dashboard I’d been meaning to build for two years and never had. It doesn’t replace the big stack where the big stack belongs, but for everything below that line, it’s the most enjoyable monitoring I’ve set up in ages.




