Bitdoze logo

Beszel & Uptime Kuma on Dokploy: Server Monitoring Guide

Set up Beszel and Uptime Kuma on Dokploy for complete Docker server monitoring: resource metrics, uptime checks, SMTP alerts, plus Beszel 0.19 and Kuma v2 tips.

Dragos

Updated Published 31 min read

Beszel hub dashboard and Uptime Kuma status page monitoring a Dokploy server, with alerts sent over SMTP

Beszel and Uptime Kuma are the two self-hosted tools I put on every Dokploy server. The split is simple: Beszel watches what’s happening inside the machine (CPU, memory, disk, Docker containers), while Uptime Kuma verifies your services actually work from the outside. Internal health and external availability fail in different ways, so you need both.

This guide walks through deploying both on Dokploy, wiring email alerts through Brevo, and connecting the two tools into a dead man’s switch so your monitoring can’t fail silently.

Verified with Beszel 0.19.0 and Uptime Kuma 2.5.4, September 2026. Both tools moved fast since this guide was first published, so version-specific steps (image tags, agent env vars, backups) are current as of that date.

More Monitoring Options

For a broader look at monitoring options like Netdata or Prometheus + Grafana, see our server monitoring tools guide.

About the Video

The video was recorded before Uptime Kuma v2 and Beszel 0.19. The written steps below are the current ones. In particular, the image tags and agent environment variables changed.

Understanding key monitoring metrics

Good self-hosted server monitoring starts with knowing which numbers matter. Everything else in this guide exists to collect these and to tell you when they go wrong:

Metric Category What to Monitor Why It Matters
CPU Load average (1, 5, 15 min), per-core usage, system/user time Identifies processing bottlenecks
Memory Available RAM, swap usage, buffer/cache utilization Prevents out-of-memory crashes
Disk Space usage, inode utilization, I/O wait times Avoids disk-full failures
Network Bandwidth, packet loss, latency, connection states Detects network issues early

In a container environment you care about these same metrics at two levels: the host (is the machine healthy?) and the container (is this app eating more RAM than it should?). Beszel handles both levels, and recent versions added more specialized collectors on top (S.M.A.R.T. disk health, systemd services, GPU usage, ZFS pools), which we’ll flag where they matter.

Why Beszel and Uptime Kuma?

Tool Purpose What It Monitors
Beszel Internal resource monitoring CPU, memory, disk, network, Docker containers, systemd, SMART, GPU, ZFS
Uptime Kuma External availability checks HTTP/HTTPS endpoints, DNS, TCP ports, SSL certificates, push heartbeats

Beszel: the lightweight monitoring hub

Beszel is a lightweight monitoring solution built around a hub and a small agent. Unlike Prometheus + Grafana, there’s no time-series database to operate and no dashboards to build. Current versions (0.19.x) give you:

  • Real-time server metrics with historical graphs
  • Docker container statistics, plus a containers page with status, health, and logs (0.14+)
  • Container health alerts that include log excerpts (0.19+)
  • S.M.A.R.T. disk health monitoring with failure alerts (0.15+)
  • systemd service monitoring, including alerts for failed services (0.16+)
  • GPU monitoring (NVIDIA via NVML, Apple Silicon, Intel Arc) (0.16-0.18+)
  • ZFS pool and dataset monitoring (0.19+)
  • Alert quiet hours to stop night-time noise (0.17+)
  • Multi-server support from a single hub, with public key or token auth
  • Update-available notice in the UI (0.18.5+)
  • MIT licensed, fully free self-hosted

Here’s the Beszel interface showing a monitored system:

Beszel dashboard showing server metrics

And the historical graphs for CPU, memory, and network:

Beszel graphs showing CPU, memory, and network usage over time

Screenshots

These screenshots are from an earlier Beszel version. The 0.19 UI looks fresher and has more pages (containers, SMART, services), but the layout concept is the same.

Beszel at a glance:

Feature Description Benefit
Lightweight Minimal resource usage (expect ~50-100MB RAM for the hub, ~10-20MB per agent) Fine for small servers
Docker integration Per-container CPU, memory, network, plus health and logs Container tracking without extra tools
Secure agent comms SSH key auth by default, optional outbound WebSocket with tokens No open management ports needed
Multi-server One hub monitors all your agents Centralized monitoring
Historical data Metrics stored with graphs over time Trend analysis

Uptime Kuma: uptime monitoring from the outside

Uptime Kuma checks whether your websites and services are reachable from the outside world and notifies you when they aren’t. Version 2 is the current stable line (v2.0 shipped October 2025), and it covers:

  • HTTP(s), keyword, JSON Query, and Websocket-upgrade monitors
  • TCP, Ping, DNS, Steam, and Docker container checks
  • NTP monitors (added in v2.5.0)
  • Push monitors: a URL your apps can ping as a heartbeat
  • SSL certificate expiration monitoring
  • Public status pages
  • 90+ notification integrations
  • Maintenance windows

Why use both together

Internal health does not equal external availability:

  • Your server can show 10% CPU usage while your site serves 502s because a container crashed
  • A container can be “running” but returning errors on every request
  • Your disk can be 95% full and Uptime Kuma won’t notice until the app falls over

Beszel tells you what’s happening inside. Uptime Kuma tells you what users experience. Together, here’s the full picture:

Architecture: Beszel hub and agent inside the Dokploy server, Uptime Kuma probing public endpoints, both alerting through Brevo SMTP, plus a dashed dead man's switch heartbeat from Beszel to a Kuma push monitor

How this stack compares

Feature Beszel + Uptime Kuma Prometheus/Grafana Netdata
Scalability Small-Medium Enterprise-grade Medium
Setup Complexity Low High Low
Customization Moderate Extensive Moderate
Resource Usage Very low (expect ~200-400MB total; check with docker stats) Moderate-High Low
Learning Curve Gentle Steep Moderate
Uptime Monitoring Built-in (Kuma) Requires additional setup Limited
Status Pages Yes (Kuma) Manual setup needed No

For 1-10 servers this combination is hard to beat. Past 20 servers or if you need custom recording rules and long retention, Prometheus + Grafana earns its complexity.

Prerequisites

  • A VPS or server: 1GB RAM works, 2GB is comfortable for this stack plus your apps
  • Dokploy installed: follow the Dokploy installation guide if you haven’t already
  • Two DNS subdomains: for example monitor.yourdomain.com (Beszel) and status.yourdomain.com (Uptime Kuma)
  • Ports: 80/443 open for the dashboards; port 45876 also reachable from the hub if you’ll monitor remote agents the classic way (explained in Part 2)
  • SSH access: for the verification commands in this guide
  • Email service (optional): a Brevo account for alerts; the free tier gives 300 emails/day

Part 2 assumes you’re comfortable deploying a raw compose file; if not, read how to deploy a Docker Compose app in Dokploy first.

Running this on a home server instead of a VPS? A compact box like the GMKtec M5 Ultra is enough for this monitoring stack plus a handful of containers.

Try Hetzner Cloud Now Try Hostinger VPS

VPS prices jumped across the board in 2026 — if you’re rethinking a rented box, see what changed and when a mini PC wins.

Part 1: Deploying the Beszel hub

Beszel has two components:

  1. The Hub: the web dashboard where you view all metrics and configure alerts
  2. The Agent: a tiny container installed on every server you want to monitor

Step 1: Create the service from the Dokploy template, then fix the tag

  1. In your Dokploy dashboard, go to your project and click Create Services → Template
  2. Search for Beszel and select it
  3. Before deploying, open the compose editor and change the image tag. At the time of writing, the Dokploy blueprint pins henrygd/beszel:0.10.2, which is several minor versions behind. Set it to henrygd/beszel:0.19 (floats within the 0.19 line) or henrygd/beszel:0.19.0 (pinned)
  4. Choose the server you want the hub on and deploy

Don't Use :latest

Avoid the latest tag entirely. Beszel has shipped broken latest pushes before (a bad push in the 0.18 cycle made agents report as down). Pin to a minor line like 0.19 so updates are deliberate.

For redundancy, deploy the hub on a different node from your main production apps. If the production server dies, the hub survives to tell you about it.

Step 2: Configure the domain

  1. Go to the Domains tab of the Beszel service
  2. Add your domain (for example monitor.yourdomain.com)
  3. Set the port to 8090 (Beszel’s default UI port)
  4. Enable HTTPS (Let’s Encrypt) and create the domain

If you use Cloudflare, create an A record pointing monitor to your server IP and set SSL/TLS mode to Full, not Flexible.

Step 3: Set essential environment variables

Two environment variables are worth setting on the hub:

bash
APP_URL=https://monitor.yourdomain.com
CHECK_UPDATES=true
  • APP_URL makes alert emails contain real links instead of localhost
  • CHECK_UPDATES (Beszel 0.18.5+) shows an update-available notice in the UI when a new version exists

Add them in the Environment tab, save, and redeploy. We’ll add a third variable later, HEARTBEAT_URL, in the dead man’s switch section.

Step 4: Initial setup and verify

  1. Visit https://monitor.yourdomain.com and create your admin account with a strong password
  2. You’ll land on an empty dashboard

Verify from your server:

bash
docker exec beszel /beszel health --url http://localhost:8090

Exit code 0 means the hub is healthy. From outside:

bash
curl -fsSIL https://monitor.yourdomain.com | head -1

Expect HTTP/2 200 or a redirect to /login. Anything else (timeouts, 5xx) means the domain or Traefik config is wrong.

Failure mode to know about: if the dashboard looks nothing like the current Beszel docs, the blueprint tag won. Check what actually deployed:

bash
docker ps --format '{{.Names}}\t{{.Image}}' | grep beszel

If it says 0.10.2 or older, fix the tag in Dokploy and redeploy.

Part 2: Installing the Beszel agent

Now install the agent on every server you want to monitor, starting with the hub’s own machine.

Step 1: Get the agent config from the hub

  1. In the Beszel hub, click Add System
  2. Name it (for example main-dokploy-node)
  3. Copy the Docker Compose snippet the dialog generates. It contains the unique public KEY for this system

Depending on your hub version, the snippet may also include HUB_URL and TOKEN for outbound WebSocket mode (more on that below). The classic shape looks like this:

yaml
services:
  beszel-agent:
    image: henrygd/beszel-agent
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./beszel_agent_data:/var/lib/beszel-agent
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      LISTEN: 45876
      KEY: "<public key from the Add System dialog>"

Three details matter here:

  • LISTEN is the current variable name. Older guides (and old copies of this article) used PORT, which stopped being the correct name around v0.12. If your agent ignores its port config, this is why.
  • network_mode: host is required so the agent can read the host’s real network interface stats.
  • The Docker socket is mounted read-only (:ro) so the agent can list containers and pull per-container stats without being able to mutate Docker.

Step 2: Deploy the agent via Dokploy

  1. In Dokploy, create a new service of type Compose (not Template)
  2. Name it beszel-agent
  3. Paste the compose config you copied from the hub
  4. Deploy

Port Must Match

The default agent port is 45876. If you change LISTEN, the hub needs to know. Keep the default and there is nothing extra to configure.

Step 3: Verify the connection

  • The system icon in the hub turns green within about a minute
  • CPU, memory, disk, and Docker stats start populating graphs
  • The agent ships a health check too:
bash
docker exec beszel-agent /agent health

Exit code 0 means the agent process is healthy. If the icon stays red, jump to the troubleshooting checklist below.

Two ways to connect agents

Beszel supports two connection models. Pick based on how many servers you have and how your firewall is set up.

Per-system key (classic)

The flow from the steps above: each system you add in the hub gets its own SSH public key, and the agent listens on port 45876 for the hub to dial in.

  • Best for 1-3 servers
  • The hub must be able to reach every agent on port 45876 (inbound rule on each server’s firewall)
  • Each Add System dialog gives you a fresh compose snippet with a unique KEY, so there are no shared secrets

Universal token + outbound WebSocket

Since Beszel v0.12.0, agents can instead dial out to the hub over WebSocket and self-register with a token. Tokens can be made permanent since v0.18.0.

  1. In the hub, go to Settings → Tokens and create a universal token
  2. Deploy this on every server:
yaml
services:
  beszel-agent:
    image: henrygd/beszel-agent
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./beszel_agent_data:/var/lib/beszel-agent
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      KEY: "<any placeholder or real key>"
      HUB_URL: "https://monitor.yourdomain.com"
      TOKEN: "<token from /settings/tokens>"
      SYSTEM_NAME: "worker-01"
  • One token works for N servers. Set SYSTEM_NAME per server so they don’t collide
  • No inbound port 45876 needed anywhere; the agent makes outbound HTTPS to the hub. Great for firewall-tight environments
  • Recommended for fleets of 4+ servers

Breaking Change in Beszel 0.19

Agents now verify HTTPS certificates when connecting to the hub. With a normal Let’s Encrypt cert from Dokploy/Traefik, nothing changes. But if your hub runs on a self-signed cert, a private CA, or an IP-based URL, the connection is rejected until you point the agent at your CA with the CA_CERT_FILE environment variable.

Troubleshooting: agent shows red

Work through this checklist in order:

  1. Port reachability (classic mode only): from the hub’s server, nc -zv <agent-ip> 45876. If it fails, open 45876 in the agent server’s firewall/security list, limited to the hub’s IP, not the world
  2. KEY mismatch: the KEY in the agent’s env must match what the hub generated for that system. Copy-paste errors are the usual cause
  3. Wrong HUB_URL/TOKEN (WebSocket mode): the URL must be exactly the hub’s public URL, and the token must not be expired
  4. Certificate rejection (0.19+): if the agent logs mention certificate errors, you’re on a non-public CA, so set CA_CERT_FILE as described above
  5. Check agent logs: docker logs beszel-agent; connection errors are explicit

Beszel also maintains a common issues page in its docs that covers the rarer cases.

Monitoring additional servers

With a universal token this is now the cheap part of the whole setup:

  1. Copy the same compose snippet to every server
  2. Change SYSTEM_NAME (for example worker-02, backup-01)
  3. Deploy. Each agent self-registers in the hub

No hub-side dialog needed per server. If you’re on the classic key flow instead, repeat Add System for each server and deploy its unique snippet.

Part 3: Setting up Uptime Kuma (v2)

Beszel watches internal resources; Uptime Kuma watches what the outside world sees.

The latest Tag Still Points to v1

Uptime Kuma’s latest Docker tag is deprecated and still serves version 1. If your compose says louislam/uptime-kuma:1 or :latest, you are running the old major, including on fresh Dokploy template deployments if you don’t check. Use louislam/uptime-kuma:2. This is the single most important fix in this article.

If you want a quicker path than manual tag surgery, we have a separate one-click Uptime Kuma deployment guide for Dokploy, and a general guide to install Uptime Kuma self-hosted on any server if you’re not using Dokploy here.

Step 1: Deploy via template, change the tag to 2

  1. In Dokploy, create a service from Template, search for Uptime Kuma, select it
  2. In the compose editor, set the image to louislam/uptime-kuma:2 (the Dokploy blueprint ships 2.1.0 at the time of writing, the right major version but behind current stable; :2 floats the whole v2 line). Current stable is 2.5.4
  3. Deploy

Two optional tweaks:

  • louislam/uptime-kuma:2-slim if you want a smaller image
  • Add UPTIME_KUMA_SQLITE_SINGLE_CONNECTION=true to the environment if you run this on a Raspberry Pi-class device and hit SQLite locking errors (available since 2.3.0)

Migrating from Uptime Kuma v1 to v2

Many existing setups are silently stuck on 1.23.x precisely because latest never left v1. The upgrade is safe if you do it in this order:

  1. Stop the container (in Dokploy: stop the service)
  2. Back up the data directory. This is the only supported backup method in v2; the old “export to JSON” feature was removed. See the backup commands in the Backups section below
  3. Change the image tag to :2 and start the service. Changing the image tag is exactly a Docker Compose stack update in Dokploy, so the same flow applies
  4. Watch the deploy logs. v2 migrates the heartbeat database on first start, and that “could take some time”. The maintainer’s reference point is around 7 minutes for 20 monitors with 90 days of history, and it can take longer on slow disks or big instances
  5. Verify your monitors and notifications came through

Do Not Interrupt the Migration

If the migration is interrupted mid-run, restore your backup and retry from a stopped state. Don’t restart the container because it “looks stuck”; check the logs for activity first.

Other v2 changes that matter once you’re migrated:

  • New monitors default to 0 retries (was 1). A single failed probe can now page you. Set retries deliberately: 2-3 for flaky endpoints, 1 for things that should never blip
  • SMTP notification templates use LiquidJS; variable names are case-sensitive, and unsupported variables are ignored. Only matters if you customized templates in v1
  • Badge :duration values are restricted to fixed formats like 24h or 30d

Step 2: Configure the domain

  1. Go to the Domains tab of the Kuma service
  2. Add status.yourdomain.com, internal port 3001, enable HTTPS, create

Volume Persistence

Dokploy handles the volume creation automatically to persist your monitoring data across container restarts. Check which volume name it generated; you’ll need it for backups.

Step 3: Initial setup

  1. Visit https://status.yourdomain.com and create the admin account
  2. You’ll see an empty dashboard

Step 4: Add your first monitors

  1. Click Add New Monitor
  2. Pick a type (the table below), enter the target
  3. Set the heartbeat interval (60s is fine) and set retries explicitly; remember the v2 default is 0
  4. Save
Monitor Type Use Case Example Suggested Retries
HTTP(s) Website availability https://yourdomain.com 2-3
HTTP(s) - Keyword Content verification Check the page contains “Welcome” 2-3
JSON Query API responses Assert $.status equals ok 2
TCP Port Database/queue connectivity PostgreSQL on 5432 (internal) 2
DNS DNS resolution Check the A record for your domain 2
Docker Container Container state via the Docker socket Monitor a specific container 1-2
NTP Time server health (v2.5.0+) Your internal NTP source 2
Push App heartbeats (dead man’s switch) See the next section 1

Verify: the monitor shows green heartbeat bars within one interval. Then test detection on purpose: stop one small app container and confirm Kuma flips it to Down and recovers it to Up after you restart it. If a single blip alerts you immediately, that’s the retries=0 default; raise it.

Monitor your monitoring: a dead man’s switch

Both dashboards share one blind spot. If the Beszel hub crashes, the whole VPS dies, or DNS breaks, nothing alerts you, because every internal check dies with the machine.

The old advice was to point a free UptimeRobot account at your Uptime Kuma instance. You don’t need a third party anymore. Since Beszel v0.18.4, the hub can send a heartbeat to any push endpoint, and Uptime Kuma has native Push monitors:

Dead man's switch flow: Beszel hub posts to a Kuma push monitor every 60 seconds, and silence past the interval and retry window triggers a Brevo email alert
  1. In Uptime Kuma, add a monitor of type Push
  2. Copy the push URL it generates (shape: https://status.yourdomain.com/api/push/<token>)
  3. Add to the Beszel hub environment in Dokploy:
bash
APP_URL=https://monitor.yourdomain.com
CHECK_UPDATES=true
HEARTBEAT_URL=https://status.yourdomain.com/api/push/<token>
  1. Redeploy the hub

Beszel now POSTs a JSON status summary (systems up/down, triggered alerts, hub version) every HEARTBEAT_INTERVAL seconds (60 by default). As long as pings arrive, the Kuma monitor shows green heartbeats. The moment they stop, Kuma declares it Down and fires your notification.

Why This Beats a Third-Party Watcher

It’s free, it stays inside your stack, and it catches the same failure class: hub dead, VPS dead, Traefik broken, DNS not resolving. The same trick works in reverse: give each critical app its own Kuma push monitor so a crashed app can’t take its own watchdog down with it.

Verify: once the push monitor shows green, stop the Beszel hub container and wait. Kuma should flip the monitor to Down after roughly interval × retries (about 1-2 minutes with defaults), then recover when you start the hub again. For more heartbeat and push-monitor patterns across different stacks, see the broader server monitoring guide.

Part 4: Configuring SMTP alerts with Brevo

Monitoring without notifications is just a pretty dashboard. Here’s how to wire email alerts through Brevo (formerly Sendinblue), which offers 300 free emails per day, plenty for alerts.

Step 1: Get SMTP details from Brevo

  1. Create a free Brevo account if you don’t have one
  2. Go to Settings → SMTP & API
  3. Generate a new SMTP key
  4. Note the credentials:
Setting Value
Server smtp-relay.brevo.com
Port 587
Login Your Brevo email
Password The SMTP key you generated

Sender Authentication

Ensure your sender address (e.g., alerts@yourdomain.com) is authenticated in Brevo. Go to Settings → Senders & IP → Senders and add/verify your domain, or every test email will bounce.

Step 2: Configure Beszel alerts

  1. In Beszel, open the admin Settings and find the SMTP section (exact tab names vary a bit between versions, but it lives under admin settings)
  2. Enter the Brevo details: host smtp-relay.brevo.com, port 587, your Brevo login, the SMTP key as password, and alerts@yourdomain.com as sender
  3. Save, then use the built-in test email button to confirm delivery

Set up alert thresholds:

  1. Go to Systems, click your server, open Alerts
  2. Enable alerts per metric; the recommended values are in the thresholds table in the Best Practices section

Beyond email, Beszel notifications are configured as Shoutrrr URLs under Settings → Notifications. That gives you native Discord, Telegram, Slack, Microsoft Teams, Gotify, Ntfy, Pushover, Matrix, Mattermost, OpsGenie, Signal, and Twilio, with no webhook glue required.

Step 3: Configure Uptime Kuma alerts

  1. In Uptime Kuma, go to Settings → Notifications
  2. Click Setup Notification, choose Email (SMTP)
  3. Fill in the same Brevo details: hostname smtp-relay.brevo.com, port 587, security STARTTLS, your Brevo login, the SMTP key as password, alerts@yourdomain.com as From
  4. Click Test and check the inbox, then save
  5. Attach the notification to each monitor (or use the “apply to all monitors” option when saving it)

One footnote if you customize email templates in v2: Kuma switched template rendering to LiquidJS, and variable names are case-sensitive.

Verify both paths: send a test email from each tool, then check the Brevo dashboard’s delivery logs. If tests arrive but real alerts don’t, the usual cause is notifications not attached to the monitors.

Part 5: Testing your monitoring stack

Untested alerting is a guess, not a system. Run all three tests after setup, and re-run them every month or after major changes.

Test 1: Beszel detects a dead agent

  1. On a monitored server: docker stop beszel-agent
  2. Wait 1-2 minutes
  3. Expect a “System is Down” email (assuming you enabled the status alert in Part 4)
  4. docker start beszel-agent and confirm the “System is Up” recovery email

Test 2: Uptime Kuma detects a dead app

  1. Stop one of your monitored apps: docker stop your-web-app
  2. Kuma should detect the failure (timeout or 502) within heartbeat interval × retries
  3. Expect the notification, then restart the app and confirm the Up notification

Test 3: The dead man’s switch catches a dead hub

  1. Stop the Beszel hub container on your Dokploy node
  2. Watch the Kuma push monitor flip to Down after about interval × retries (1-2 minutes with defaults)
  3. Expect your notification, then start the hub again

If any test fails silently, fix it now. This is exactly the failure class you built the stack to catch.

Backups, updates, and costs

The stack is only done when it survives a server rebuild. Here’s the operator floor.

Backing up Uptime Kuma

In v2, backing up the data directory is the only supported backup method; the old JSON export was removed. First find the real volume name (Dokploy’s naming won’t match generic examples):

bash
docker volume ls | grep -i kuma

Then back it up:

bash
docker run --rm -v <kuma_volume>:/data -v $(pwd):/backup alpine \
  tar czf /backup/kuma-backup.tar.gz -C /data .

Schedule it: a weekly cron is fine since the data changes slowly, or use Dokploy’s built-in backups. For offsite copies, see how to set up automated Dokploy backups to Cloudflare R2. A backup you’ve never restored is a hope, not a backup: do one test restore.

Backing up Beszel

The hub stores everything in its data volume (beszel_data in the default compose). Admin settings include a Backups section. Check what your version offers there and back the volume up the same way as Kuma’s if you want full control. If you lose admin access, recover it directly:

bash
docker exec beszel /beszel superuser upsert you@example.com newpassword

Updating safely

  • Beszel: keep CHECK_UPDATES=true so the hub tells you when a release exists. Pin to the minor line (0.19) and read the release notes before jumping. 0.19’s agent certificate verification is a good example of a change that bites unprepared setups
  • Uptime Kuma: pin the major (:2), and back up the data directory before every upgrade. Then redeploy via Dokploy and watch the logs

What this costs

Both tools are MIT-licensed and free; no paid tier is required for any feature in this guide. Brevo’s free tier covers 300 alert emails/day. The real cost is zero extra infra: the whole stack fits comfortably on the VPS you already run. Expect roughly 50-100MB RAM for the hub, 10-20MB per agent, and 100-200MB for Kuma. Get real numbers on your node with docker stats.

Your monthly operating checklist:

  • Backup the Kuma data directory and the Beszel volume (weekly is fine)
  • Test-restore a backup once, so you know the procedure works
  • Re-run the three alert tests monthly
  • Review thresholds after adding new services
  • Apply updates at least monthly, reading release notes first

Best practices and security hardening

  • Set meaningful thresholds: alert on sustained problems, not every CPU spike. Use Beszel’s quiet hours (0.17+) to mute non-urgent alerts at night
  • Use multiple channels: email for everything, Discord/Telegram/Pushover for critical alerts
  • Test alerts monthly: the three tests in Part 5 take five minutes
  • Document your monitors: a one-line comment per monitor saying what it checks and why it exists
  • Monitor your monitoring: the dead man’s switch from the previous section is not optional if this stack matters

Starting points that avoid alert fatigue. Tune to your workload:

Metric Warning Critical Notes
CPU usage 70% for 5 min 90% for 5 min Sustained high CPU indicates a bottleneck
Memory usage 75% 85% Leave headroom for spikes
Disk usage 80% 90% Plan expansion before critical
Disk I/O wait 20% 40% High I/O wait slows everything
Container restarts 2 in 10 min 5 in 10 min Crash loops need investigation
System status n/a Down Immediate alert

Beszel 0.19 can also alert natively on things the older versions couldn’t: container health (with log excerpts in the notification), failed systemd services, CPU I/O wait and steal time, GPU usage, temperature, S.M.A.R.T. failure, low battery, bandwidth, and load average. If you came from a cron-and-email setup, like the classic approach to monitoring CPU usage and sending email alerts on Linux, this replaces all of that scripting.

Security hardening

A monitoring tool is, by definition, a map of your entire infrastructure. Treat it as sensitive:

  • Docker socket: read-only mount is the baseline. For tighter setups, run a socket proxy like linuxserver/docker-socket-proxy with CONTAINERS=1 and point the agent’s DOCKER_HOST at it; the agent gets container stats and nothing else. (Setting DOCKER_HOST="" disables Docker monitoring entirely if you want metrics-only.)
  • Agent port: in classic mode, firewall 45876 to the hub’s IP only. Or skip the inbound port altogether with the WebSocket token mode from Part 2
  • Beszel auth: beyond password auth you get OAuth/OIDC (with DISABLE_PASSWORD_AUTH to turn passwords off entirely), MFA_OTP email one-time passwords (requires SMTP configured first), and TRUSTED_AUTH_HEADER for Cloudflare Access-style fronting. Use the Admin/User/Read-only roles if you share the dashboard
  • Uptime Kuma: strong admin password, keep the admin UI private, and let only the status pages be public if you need public ones

These overlap heavily with the checklist in our guide on how to secure a Docker server. Apply that checklist to the whole node, not just the monitoring stack.

When to scale up

  1. 1-5 servers: this Beszel + Uptime Kuma stack is all you need
  2. 5-20 servers: same stack works; consider adding Netdata on the noisy nodes for deeper per-metric drilldowns
  3. 20+ servers or compliance needs: time to move to Prometheus + Grafana

Conclusion

Beszel for internal resource metrics, Uptime Kuma for external availability, both self-hosted on Dokploy, both wired into Brevo alerts, and both watching each other through a push heartbeat. That’s a complete monitoring stack that adds nothing to the monthly bill.

Both tools grew real capabilities since this guide was first written. Beszel 0.19 now covers ZFS pools, SMART disk health, GPU usage, systemd services, and container health alerts with logs. Uptime Kuma v2 added JSON Query and NTP monitors. Same two containers, far more coverage.

Your Situation Recommended Approach
Beginner with 1-2 servers Start with this Beszel + Uptime Kuma stack
Need log monitoring too Add Dozzle for Docker logs
Want cloud-managed option Consider Netdata Cloud (free for 5 nodes)
Not on Dokploy yet Compare the best self-hosted server panels first
Enterprise with complex needs Prometheus + Grafana stack

For the full menu of options, including Netdata and Prometheus, see our server monitoring tools guide.

Beszel on GitHub Uptime Kuma on GitHub

Frequently asked questions

What's the difference between Beszel and Uptime Kuma?

Beszel monitors internal server resources: CPU usage, memory, disk space, network bandwidth, Docker container stats, and (in recent versions) SMART disk health, systemd services, GPU usage, and ZFS pools. It tells you what’s happening inside your server.

Uptime Kuma monitors external availability: whether your websites respond, APIs return valid data, and services are reachable from outside. It tells you what users experience.

You need both because a server can have healthy internal metrics but still serve errors to users, and an app can be down while the server itself looks perfectly fine.

Are Beszel and Uptime Kuma free?

Yes, both. Both projects are MIT-licensed and fully free to self-host. There’s no paid tier gating features, no per-monitor limits, no license keys.

Your only potential costs are the infrastructure they run on (any small VPS handles the whole stack) and an email service for alerts if you exceed free tiers; Brevo’s free 300 emails/day covers alert volume for almost any homelab or small production setup.

How much RAM and CPU do they use?

Expect roughly:

  • Beszel Hub: ~50-100MB RAM
  • Beszel Agent: ~10-20MB RAM per monitored server
  • Uptime Kuma: ~100-200MB RAM depending on monitor count and history

Combined, plan for a few hundred megabytes, an order of magnitude less than a Prometheus + Grafana stack. CPU is negligible at idle on all three. For real numbers on your own node, run docker stats and read the actual usage; the numbers above are planning figures, not guarantees.

Can I use other notification services besides email?

Yes, both tools go well beyond email.

Beszel configures notifications as Shoutrrr URLs under Settings → Notifications, with native support for Discord, Telegram, Slack, Microsoft Teams, Gotify, Ntfy, Pushover, Matrix, Mattermost, OpsGenie, Signal, and Twilio, among others.

Uptime Kuma has 90+ built-in notification integrations, including Slack, Discord, Teams, Telegram, Pushover, Gotify, PagerDuty, Opsgenie, SMS services, and generic webhooks.

How do I backup my monitoring data?

Uptime Kuma (v2): backing up the data directory is the only supported backup method; the JSON export feature was removed in v2. First check your actual volume name with docker volume ls (Dokploy’s naming differs from generic docs), then:

bash
docker run --rm -v <kuma_volume>:/data -v $(pwd):/backup alpine \
  tar czf /backup/kuma-backup.tar.gz -C /data .

Beszel: the hub keeps everything in its data volume; admin settings also expose a backups section. If you’re locked out, recover admin access with:

bash
docker exec beszel /beszel superuser upsert you@example.com newpassword

For offsite, scheduled copies of both, follow the guide to set up automated Dokploy backups to Cloudflare R2.

My Beszel agent shows red and won't connect. What do I check?

In order:

  1. Firewall: in classic key mode, the hub must reach the agent on port 45876. Allow it for the hub’s IP only. Not needed on the same host, and not needed at all in WebSocket token mode
  2. KEY mismatch: the agent’s KEY must be the one generated for that specific system
  3. HUB_URL/TOKEN (WebSocket mode): must be the hub’s exact public URL and a valid, non-expired token
  4. Certificate verification (Beszel 0.19+): agents now verify the hub’s HTTPS certificate. Self-signed or private-CA hubs need CA_CERT_FILE configured on the agent; public Let’s Encrypt certs need nothing
  5. Read docker logs beszel-agent; connection failures are logged explicitly

Beszel’s docs also keep a common issues page that covers rarer cases.

Can I monitor servers that aren't running Dokploy?

Yes. The Beszel agent is a single container (or static binary) that runs anywhere:

  1. Docker/Podman on any Linux server
  2. The binary directly, without Docker
  3. Windows and FreeBSD hub builds exist too (0.17+)

As long as the agent can reach the hub (port 45876 inbound, or outbound WebSocket with a universal token), it shows up in the same dashboard. The universal token flow makes this trivial for mixed fleets: one snippet, one token, every server.

Can I create public status pages?

Uptime Kuma has built-in status pages:

  1. Go to Status Pages in the menu
  2. Create a new status page and pick a slug
  3. Add the monitors to display and customize the look
  4. Share the public URL

You can run multiple status pages, one public for users and one internal for the team. Keep the Kuma admin UI private and only expose the status page domain publicly.

Can I monitor extra disks or ZFS pools with Beszel?

Yes. For extra filesystems, mount them under /extra-filesystems in the agent’s compose:

yaml
volumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro
  - /mnt/disk/.beszel:/extra-filesystems/sda1:ro

Since 0.19, Beszel also monitors ZFS pools and datasets natively, and S.M.A.R.T. disk health (with failure alerts) has been built in since 0.15, so NAS drives and pools no longer need external tooling for basic health tracking.

Why did my new Uptime Kuma monitor alert after a single failed check?

That’s a v2 behavior change: new monitors default to 0 retries (v1 defaulted to 1). With zero retries, one missed probe flips the monitor Down and pages you immediately.

Fix it per monitor: set retries to 2-3 for endpoints behind flaky networks or cron-driven backends, and keep 0-1 only for things that should never blip. Retrying trades detection speed for noise resistance, so decide deliberately per monitor instead of living with the default.

How does Beszel compare to Netdata or Prometheus?
  • Beszel: lightest footprint, simplest setup, and no longer “basic”: containers with health and logs, SMART, systemd, GPU, and ZFS monitoring are all built in now
  • Netdata: more metrics per node out of the box, ML anomaly detection, a cloud option free for 5 nodes
  • Prometheus/Grafana: the most customizable and enterprise-grade, at the cost of operating a time-series database and building dashboards yourself

For 1-10 servers, Beszel + Uptime Kuma gives you the best coverage-to-effort ratio. Move to Prometheus + Grafana when you need custom recording rules, very long retention, or a large fleet.