Beszel: Lightweight Server Monitoring for Your Homelab
Why Beszel Beats Prometheus for Small Homelabs
If you're managing more than three servers in your homelab, you need centralized visibility—but Prometheus + Grafana feels like overkill when you're running a NAS, a media server, and a Kubernetes node from spare hardware. Beszel solves this by shipping a lightweight agent that consumes ~15MB RAM per node while giving you real-time CPU, memory, disk, and network metrics in a clean web UI.
I've been running Beszel in my homelab for eight months across a T5810 workstation, a QNAP NAS, and three Raspberry Pis. The setup takes roughly 20 minutes, requires minimal configuration, and actually alerts you when things go wrong—unlike dashboards that look pretty while your storage fills silently.
This post walks you through deploying Beszel hub (the central controller) via Docker, adding multiple monitored systems, configuring alerts, and avoiding the permission issues that catch most first-time users.
Prerequisites and Your Environment
You'll need:
- Beszel 0.7.1 or later (the latest stable release; earlier versions had TLS handshake bugs)
- Docker and Docker Compose 2.20+ installed on your hub machine
- Ubuntu 22.04 LTS or later, Debian 12+, or equivalent on monitored systems (Beszel agent is statically compiled, so it runs almost anywhere)
- Network connectivity between hub and agents on port 25001 (configurable, but don't use 25000)
- Optional: reverse proxy (Traefik, Caddy) if you want web access outside your LAN
This guide assumes you're comfortable with Docker, comfortable editing YAML, and you have at least two systems to monitor (hub + one agent minimum).
Deploy Beszel Hub via Docker Compose
Start by creating a dedicated directory and docker-compose.yml file on your hub machine. I use /opt/beszel to keep things clean.
mkdir -p /opt/beszel/data
cd /opt/beszel
Create your docker-compose.yml:
version: '3.8'
services:
beszel:
image: henrygd/beszel:0.7.1
container_name: beszel-hub
restart: always
ports:
- "8090:8090"
environment:
# Generate this with: docker run --rm henrygd/beszel:0.7.1 beszel gensecret
# Store it somewhere safe—you'll need it for agent pairing
SECRET_KEY: "your-generated-secret-here"
# Optional: enable debug logging
# DEBUG: "true"
volumes:
- ./data:/beszel_data
networks:
- beszel
# Optional: lightweight reverse proxy for HTTPS (Caddy)
caddy:
image: caddy:2.8.1-alpine
container_name: beszel-caddy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./certs:/data/caddy/certificates
- ./config:/config/caddy
networks:
- beszel
depends_on:
- beszel
networks:
beszel:
driver: bridge
Generate a secret key first—this is non-negotiable:
docker run --rm henrygd/beszel:0.7.1 beszel gensecret
You'll get output like zbVk9m4nXpLq2wR8vT3jK5zA6bC7dE9f. Paste that into the SECRET_KEY environment variable. Now start the hub:
docker compose up -d
docker compose logs -f beszel
Wait for the log line: listening on 0.0.0.0:8090. Hit http://localhost:8090 and you'll see the Beszel login screen. Default credentials are admin / admin—change this immediately in Settings.
HTTPS with Caddy (Optional but Recommended)
If you're accessing Beszel over your LAN via IP address, skip this. If you have a local domain (e.g., beszel.home.local) and want TLS, create a Caddyfile:
beszel.home.local {
reverse_proxy beszel:8090
tls internal
}
Then uncomment the Caddy service and run docker compose up -d. Caddy auto-generates a self-signed cert with its internal CA—your browser will complain, but the connection is encrypted.
Gotcha #1: If Caddy fails to start, check that port 80 isn't already bound. Run sudo lsof -i :80 to find conflicts. On systems with systemd-resolved listening on 127.0.0.53:53, you might have a DNS conflict too—less common but worth checking if you're also using Pi-hole.
Install and Pair Beszel Agents on Monitored Systems
The Beszel agent is a single statically-compiled binary. Installation takes one command per server.
On Linux Systems (Ubuntu, Debian, Raspberry Pi OS)
SSH to your first monitored system and run:
curl -sL https://raw.githubusercontent.com/henrygd/beszel/main/agent/install.sh | sh
This downloads the binary to /usr/local/bin/beszel-agent and creates a systemd service. Enable and start it:
sudo systemctl enable beszel-agent
sudo systemctl start beszel-agent
sudo systemctl status beszel-agent
Check the logs:
sudo journalctl -u beszel-agent -f
You should see listening on 0.0.0.0:25001.
Pairing Agents with the Hub
Back on your hub, open http://localhost:8090 and go to Add System. You'll be presented with a pairing key—something like abc123def456. This is a one-time token.
On the monitored system, run:
sudo beszel-agent -s <hub-ip>:25001 -p <pairing-key>
Example:
sudo beszel-agent -s 192.168.1.100:25001 -p abc123def456
If pairing succeeds, the agent stores the hub's public key in /etc/beszel/agent.conf and closes the connection. Refresh the hub dashboard—your system appears under Active Systems.
Gotcha #2: If pairing fails with "connection refused," verify the firewall on the hub machine allows port 25001. Run sudo ufw allow 25001/tcp on Ubuntu/Debian with UFW enabled. If you're behind a home router, the hub and agent must be on the same LAN (port 25001 can't traverse NAT without painful port forwarding).
Configure Real-Time Metrics and Alerts
Once systems are paired, Beszel collects CPU, memory, disk, network, and process data automatically. The dashboard updates every 5 seconds by default. You get a quick overview, but alerts are where monitoring becomes useful.
Setting Up CPU and Memory Alerts
In the hub UI, click on any monitored system. Under Alerts, create a rule:
- Condition: CPU usage > 80%
- Duration: 5 minutes (prevents noise from brief spikes)
- Action: Webhook or Email (if configured)
Set a second alert for memory:
- Condition: Memory usage > 85%
- Duration: 2 minutes
And disk—this one catches silent failures:
- Condition: Disk usage > 90%
- Duration: 1 minute (act quickly on storage)
Webhook Alerts to Discord or Slack
Beszel sends alerts as JSON POST requests to a webhook URL. For Discord, create a webhook in your server settings, then paste the webhook URL in the Beszel UI.
The payload looks like:
{
"system_name": "nas-01",
"metric": "cpu",
"value": 92,
"threshold": 80,
"timestamp": "2024-03-15T14:32:18Z"
}
Discord webhooks accept this directly. If you want formatted messages, use a simple ntfy.sh bridge or write a tiny Python webhook receiver—but for homelabs, Discord native is fine.
Multi-Server Dashboard and Data Retention
The Beszel dashboard shows all paired systems in one view. Each system tile displays current CPU, memory, and disk percentage. Click any tile to see detailed graphs, process lists, and network interfaces.
Storage and Metrics Retention
Beszel stores metrics in SQLite (the ./data volume in docker-compose). By default, it keeps 30 days of 5-minute aggregated data. This uses ~50MB per monitored system per month—negligible on any modern hardware.
If you want longer retention, edit the hub environment variables:
environment:
SECRET_KEY: "your-secret"
# Keep 60 days instead of 30
RETENTION_DAYS: "60"
Restart the container, but note: this increases database size and query latency slightly. At 30 days, queries are instant. At 180 days, you might see 100-200ms delays. For a homelab, 30-45 days is the sweet spot.
Exporting Metrics
Beszel exports Prometheus-compatible metrics at http://localhost:8090/metrics. You can scrape this with Prometheus if you already run it, but honestly, if you're not already using Prometheus, Beszel's built-in alerts are cleaner than maintaining a full stack.
Common Issues and Troubleshooting
Agent Fails to Pair: "TLS Handshake Error"
This typically happens with Beszel < 0.6.5. Update the hub:
cd /opt/beszel
docker compose pull
docker compose up -d
Then retry pairing.
Dashboard Shows "System Offline" But Agent Is Running
Check agent logs on the monitored system:
sudo journalctl -u beszel-agent -n 50
If you see "connection refused," the agent can't reach the hub. Verify:
telnet <hub-ip> 25001
If telnet hangs, the firewall is blocking. If it refuses immediately, the hub isn't listening—check hub logs:
docker compose logs beszel | tail -20
High CPU Usage on the Hub
If you're monitoring 20+ systems, the hub can spike to 5-10% CPU on aggregation cycles. This is normal and happens every 30 seconds. If it's constant 30%+, you likely have a misconfigured alert query or a custom script running. Check the hub logs for errors.
Metrics Disappear After Container Restart
You're probably not using a volume mount properly. Verify your docker-compose mount:
docker inspect beszel-hub | grep -A 5 Mounts
You should see a bind mount pointing to /opt/beszel/data on the host. If it's pointing to a Docker volume instead, your data is ephemeral. Fix it and re-mount the volume.
What You've Built
You now have a lightweight, self-contained beszel homelab monitoring setup that:
- Runs on minimal hardware (the hub uses ~30MB RAM even with 10 agents)
- Sends real-time alerts via webhook when thresholds breach
- Provides 30+ days of historical metrics for troubleshooting
- Requires zero configuration after initial pairing
Next steps: If you add more systems, the process is identical—install the agent, generate a pairing key, run the pairing command. Set up webhook alerts once, and you're covered for all new systems. If you find yourself monitoring 50+ servers, consider whether you need to graduate to Prometheus + Grafana, but honestly, most homelabs thrive on Beszel's simplicity.
For additional configuration options (custom alert thresholds, agent security hardening), see the Beszel GitHub repository.
Disclosure: This post contains affiliate links. If you purchase through these links, we may earn a small commission at no extra cost to you. We only recommend services we've tested and trust.