Why proactive monitoring matters, and why classic tools fall short
Reactive alerting — the kind that arrives after the client has already called — does not protect the relationship. It repairs it. The difference between the two is not luck: it is coverage.
An availability monitoring tool like Uptime Kuma checks that a service responds on a port or URL. It does not know whether PostgreSQL is running outside of that port, whether container memory is about to saturate, or whether the log disk is filling at 200 MB per hour. Conversely, a system metrics tool like Beszel continuously monitors each host's resource consumption, but does not test application services end-to-end.
The case documented on Lemmy on 11 October 2026 (112 votes) illustrates exactly this: PostgreSQL had stopped on a VPS without triggering any disk alert. No space threshold had been crossed. Only an application-level check would have detected the stop — which the operator did not have in place.
The Beszel + Uptime Kuma v2 stack addresses both angles. Beszel monitors each host's metrics (CPU, RAM, disk, network, containers) from a central hub. Uptime Kuma v2 checks the availability of exposed services with configurable alert channels. Both are open source, self-hosted, and install on a VPS dedicated to supervising your fleet.
What this stack delivers to an agency
- Unified view of all your client servers in a single Beszel interface, without multiplying SSH access.
- Metric alerts (CPU, RAM, disk, network) with configurable thresholds per host.
- HTTP(s), TCP, DNS, and SSL certificate availability checks via Uptime Kuma v2 — every 60 seconds by default.
- Telegram or SMTP notifications live within the first hour, no third-party subscription required.
- Detection of disk saturation or memory leaks before they block an application service.
- Public Uptime Kuma status pages shareable with end clients.
- Total stack consumption under 150 MB of RAM on the monitoring VPS.
- Reproducible deployment via Docker Compose, versionable and redeployable in minutes.
Resource requirements
To host the Beszel hub and Uptime Kuma v2 on a dedicated monitoring VPS:
Minimum resources. A 1 vCPU VPS with 1 GB of RAM is sufficient to monitor around ten hosts with Beszel and around fifty monitors with Uptime Kuma. In practice, Beszel hub uses around 50 MB of RAM and Uptime Kuma v2 around 100 MB — under 200 MB total at rest, according to community measurements. For a fleet of 20 to 50 VPS, plan for 2 GB of RAM. Beszel stores data in SQLite; allow 5 to 10 GB of disk to retain several months of history.
Software requirements. Docker and Docker Compose installed on the monitoring VPS. A domain or subdomain pointing to this VPS (monitoring.your-domain.com) for the reverse proxy and SSL. Ports 80 and 443 open for the reverse proxy, and port 8090 or any port of your choice for the Beszel hub.
On supervised VPS. Each host to be monitored only receives the Beszel agent — a lightweight binary or Docker container using around 10 MB of RAM. No public firewall changes are required: the agent initiates the connection to the hub, not the other way around.
Full deployment: Beszel hub, agents, Uptime Kuma v2 and alerts
Create the Docker Compose structure on the monitoring VPS
Connect to the VPS dedicated to monitoring. Create a working directory and the Compose file:
mkdir -p /opt/monitoring && cd /opt/monitoringCreate
docker-compose.ymlwith both services:services: beszel: image: henrygd/beszel:latest restart: unless-stopped volumes: - ./beszel_data:/beszel_data ports: - "8090:8090" environment: - DATA_DIR=/beszel_data uptime-kuma: image: louislam/uptime-kuma:2 restart: unless-stopped volumes: - ./uptime-kuma-data:/app/data ports: - "3001:3001" healthcheck: test: ["CMD", "node", "extra/healthcheck"] interval: 30s timeout: 10s retries: 3Note the
healthcheckblock on Uptime Kuma: it is essential to avoid the crash-loop trap (see the Pitfalls section).Start the stack and create administrator accounts
Start both containers:
docker compose up -dCheck that both containers are in
healthyorrunningstate:docker compose psAccess Beszel at
http://<vps-ip>:8090and create the administrator account on first access. Do the same for Uptime Kuma athttp://<vps-ip>:3001. Both interfaces should then be placed behind a reverse proxy with SSL to avoid exposing them over plain HTTP.Deploy the Beszel agent on each VPS to monitor
On each client VPS to be monitored, copy the public key generated in the Beszel interface (Settings → SSH Key), then run:
curl -sL https://raw.githubusercontent.com/henrygd/beszel/main/supplemental/scripts/install-agent.sh | bash -s -- -p 45876 -k "<your-public-key>"Alternatively, use Docker on the supervised host:
services: beszel-agent: image: henrygd/beszel-agent:latest restart: unless-stopped network_mode: host volumes: - /var/run/docker.sock:/var/run/docker.sock:ro environment: - KEY=<your-public-key> - PORT=45876In Beszel, add each host from the interface (Systems → Add System) with its IP and agent port. The connection is established from the hub to the agent: the supervised host does not need to expose this port publicly.
Configure metric alerts in Beszel
In Beszel, open a system's detail page and go to the Alerts tab. Configure thresholds on critical metrics:
- Disk: alert at 80% and critical at 90% — covers the PostgreSQL scenario from the opening case.
- RAM: alert if usage exceeds 85% sustained for 5 minutes.
- CPU: alert if load exceeds 90% for more than 3 minutes.Beszel supports Telegram, Discord, Slack, Gotify, Ntfy and generic webhooks. For Telegram, you need a bot token (obtained via
@BotFather) and your chat or group ID.Configure Uptime Kuma v2 monitors and SMTP or Telegram alerts
In Uptime Kuma, start by configuring a notification channel (Settings → Notifications). Two common options:
Telegram. Enter the bot token and chat ID. The same bot as for Beszel works — use a single bot for both tools and distinguish them by different channels.
SMTP. Enter the SMTP server, port (587 with STARTTLS), credentials, and destination address. Use a dedicated account for alert sending.
Then create monitors for each critical service: HTTP(s) URL for websites, TCP for databases and application ports. For the PostgreSQL case, add a TCP monitor on port
5432for each VPS hosting an instance — if PostgreSQL stops, the port stops responding and the alert fires within seconds.Put everything behind a reverse proxy with SSL
Add an nginx or Caddy service to the Compose to expose Beszel and Uptime Kuma over HTTPS:
caddy: image: caddy:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/dataWith a minimal
Caddyfile:monitoring.your-domain.com { reverse_proxy beszel:8090 } status.your-domain.com { reverse_proxy uptime-kuma:3001 }Caddy automatically issues and renews Let's Encrypt certificates. Remove the
portssections from beszel and uptime-kuma in the Compose once they are proxied — they should no longer be directly accessible.Verify the setup and configure data retention
Test everything by manually stopping a service on a test VPS (
docker compose stop <service>) and verifying that the Telegram or SMTP alert arrives within two minutes.For Beszel, configure data retention in Settings → Retention: the default is 7 days for high-resolution data and 30 days for hourly aggregates. Adjust based on your disk capacity.
For Uptime Kuma, retention is configured in Settings → Advanced → Auto Cleanup. Keep at least 90 days to have monthly uptime trends to present to clients.
Pitfalls to know before going to production
The Docker crash-loop reported as UP — Uptime Kuma issue #7956. Without a healthcheck in the Compose, Docker may report an Uptime Kuma container as running while it is in a restart loop. The Uptime Kuma monitor watching itself (TCP on port 3001) would show a false positive: the port responds during the brief window between two restarts. The workaround is exactly the healthcheck block shown in step 1 — it uses the script embedded in the image to test the application's internal state, not just process presence. Docker then marks the container as unhealthy if the check fails three times in a row, and the Uptime Kuma monitor sees it.
Beszel agent with network_mode: host. If supervised hosts run containers, the Beszel agent needs access to the Docker socket (/var/run/docker.sock) to report per-container metrics. In bridge network mode the socket is accessible but the IP reported by the agent may be the internal Docker network address. Prefer network_mode: host on the agent to ensure the Beszel hub sees the server's public IP.
Beszel alerts do not replace application-level checks. Beszel monitors system resources and Docker container availability. It does not test that an application correctly responds to an HTTP request or that PostgreSQL accepts connections. These two levels are complementary: Beszel detects the saturation that precedes an outage, Uptime Kuma confirms the outage when it happens.
Alert coverage by stack configuration
Scroll the table
| Failure scenario | Beszel only | Uptime Kuma only | Beszel + Uptime Kuma v2 |
|---|---|---|---|
| Disk full at 95% | Alert at configured threshold | Not detected | Beszel alert before failure |
| PostgreSQL stopped (TCP port down) | Not detected (non-Docker service) | TCP alert in < 2 min | Uptime Kuma TCP alert |
| Container in crash-loop | Alert if CPU/RAM impacted | False positive without healthcheck | Detected if healthcheck in place |
| Progressive memory leak | Alert on RAM threshold | Not detected | Anticipatory Beszel alert |
| SSL certificate expiring in 7 days | Not monitored | SSL expiry alert | Uptime Kuma SSL alert |
| VPS unreachable (network or host down) | Hub loses agent | TCP/HTTP alert | Double signal, convergent |
Hardening: isolate the monitoring VPS
The monitoring VPS is a critical asset: if it goes down, you lose visibility across everything else. Three rules to harden it.
First, do not host client applications on this VPS. Its load must remain predictable and its sole role is monitoring.
Second, close all unnecessary ports with your VPS firewall. Only 80, 443, and the SSH port (moved to a non-standard port) should be accessible from the internet. The Beszel agent port (45876 by default) should only be open from the IPs of your other VPS, not from the entire internet.
Third, add an external Uptime Kuma monitor — for example from a free HTTP check service — that verifies your own Uptime Kuma instance responds. Without this external layer, a failure of the monitoring VPS itself goes unnoticed.
Troubleshooting common issues
Beszel agent appears offline immediately after adding. Check that the agent port (45876 by default) is reachable from the hub. On the supervised host, test with nc -zv <hub-ip> 8090 — if the connection fails, the problem is with the hub's firewall. The agent itself does not require any inbound port to be open.
Uptime Kuma is not receiving Telegram alerts. The most common cause is a clock drift on the container. Check with docker exec uptime-kuma date that the time matches real UTC time. If not, add /etc/localtime:/etc/localtime:ro to the Uptime Kuma service volumes.
Beszel hub does not start or loses data after a restart. Check that the ./beszel_data volume belongs to the UID used by the Beszel container. A permissions error on the data directory prevents SQLite from writing: chown -R 1000:1000 ./beszel_data fixes the issue in most cases.
Uptime Kuma unhealthy at startup. The v2 image can take 30 to 60 seconds to fully start before the healthcheck passes. This is normal. If the container stays unhealthy after two minutes, check the logs: docker compose logs uptime-kuma.
Going further
This stack covers the two essential levels: system metrics and availability checks. For a third level — traceability and fine-grained load trend analysis — the Grafana + Prometheus stack is a natural next step. It is more resource-intensive (plan for 2 GB of dedicated RAM), but opens up PromQL and custom dashboards. The article VPS monitoring with Grafana and Prometheus covers that deployment step by step.
For backups of the monitoring VPS itself — to avoid rebuilding all the configuration after a loss — the 3-2-1 strategy with Restic and S3 storage applies directly to this stack's Docker volumes.
If you manage several client servers from a single platform, the /solutions/agences page details how ServOrbit centralises fleet monitoring on a VPS Start.