Deploy Beszel on a RamNode VPS
Lightweight hub-and-agent server monitoring backed by PocketBase. CPU, memory, disk, network, and Docker container metrics — all under 50 MB of RAM.
Part of a series: This standalone guide covers Beszel deployment end-to-end. For a broader look at monitoring tools, see the VPS Monitoring & Observability Series.
What Is Beszel?
Beszel is a lightweight, open-source server monitoring platform built around a hub-and-agent architecture. The hub is a web application (backed by PocketBase) where you view dashboards, configure alerts, and manage users. Agents are small Go binaries that run on each server you want to monitor and relay metrics back to the hub.
Compared to heavier stacks like Prometheus and Grafana, Beszel is considerably lighter on resources. Even running the hub alongside five or more agents, CPU usage typically stays under 2% on a modest VPS.
Prerequisites
- A RamNode KVM VPS running Ubuntu 22.04 or 24.04
- A non-root sudo user (initial server setup guide)
- Docker Engine and Docker Compose installed (Docker installation guide)
- UFW enabled and configured
- A domain or subdomain pointing to your VPS IP (e.g.,
monitor.yourdomain.com) - nginx and Certbot installed (
sudo apt install nginx certbot python3-certbot-nginx -y)
Open the Required Firewall Ports
Beszel uses port 8090 for the hub web interface and 45876 for agent-to-hub communication. You'll put nginx in front of 8090, so only expose 80 and 443 publicly.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcpIf monitoring only the local VPS, you do not need to open port 45876 — the hub and agent communicate over a Unix socket. For remote servers, allow the agent port only from trusted IPs:
sudo ufw allow from YOUR_REMOTE_SERVER_IP to any port 45876 proto tcp
sudo ufw reload
sudo ufw statusImportant: Docker bypasses UFW by writing directly to iptables. To prevent Docker from exposing port 8090 publicly, bind it to localhost only in the Compose file (shown in Step 2).
Create the Beszel Directory and Compose File
sudo mkdir -p /opt/beszel
cd /opt/beszelCreate the Docker Compose file. This deploys the hub and a local agent together, with the hub bound to localhost so nginx is the sole public entry point.
services:
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: unless-stopped
ports:
- "127.0.0.1:8090:8090"
volumes:
- ./beszel_data:/beszel_data
- ./beszel_socket:/beszel_socket
environment:
APP_URL: "https://monitor.yourdomain.com"
beszel-agent:
image: henrygd/beszel-agent:latest
container_name: beszel-agent
restart: unless-stopped
network_mode: host
volumes:
- ./beszel_agent_data:/var/lib/beszel-agent
- ./beszel_socket:/beszel_socket
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
LISTEN: /beszel_socket/beszel.sock
HUB_URL: "http://localhost:8090"
TOKEN: "REPLACE_WITH_TOKEN"
KEY: "REPLACE_WITH_KEY"Replace monitor.yourdomain.com with your actual subdomain. Leave TOKEN and KEY as placeholders — you'll fill them in after first login in Step 5.
Start the Hub Container
Start only the hub first, leaving the agent offline until you have credentials:
cd /opt/beszel
sudo docker compose up -d beszelsudo docker ps
sudo docker logs beszelYou should see the PocketBase server start on port 8090 with no errors. The agent will fail at this point because the token and key are not set yet — that's expected.
Configure nginx and Obtain SSL
sudo nano /etc/nginx/sites-available/beszelserver {
listen 80;
server_name monitor.yourdomain.com;
client_max_body_size 10M;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_http_version 1.1;
proxy_read_timeout 360s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}The Upgrade and Connection headers are required. Without them, WebSocket connections that agents use to communicate with the hub will fail even though the dashboard loads fine.
sudo ln -s /etc/nginx/sites-available/beszel /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Obtain SSL certificate
sudo certbot --nginx -d monitor.yourdomain.com
# Verify and reload
sudo nginx -t
sudo systemctl reload nginxCreate Your Admin Account and Get Agent Credentials
Open https://monitor.yourdomain.com in your browser. Beszel will prompt you to create the first admin account.
After logging in, click Add System in the top right. Fill in:
- Name: something descriptive, e.g.,
ramnode-hub - Host / IP: the hostname or IP of this server
- Port:
45876
Beszel generates a unique public key and token for this agent connection. Copy both values — you need them in the next step.
Note: Beszel requires HTTPS for the "Copy to clipboard" button to work. If you set up SSL correctly in Step 4, the copy button will function normally.
Update the Agent and Start It
Open the Compose file and replace the placeholder values with the credentials from Step 5:
environment:
LISTEN: /beszel_socket/beszel.sock
HUB_URL: "http://localhost:8090"
TOKEN: "your-actual-token-here"
KEY: "ssh-ed25519 AAAA...your-actual-key-here"cd /opt/beszel
sudo docker compose up -d beszel-agent
sudo docker logs beszel-agentINFO WebSocket connected host=monitor.yourdomain.comThe system you added should turn green in the dashboard within a few seconds and begin displaying CPU, memory, disk, and network graphs.
Configure Alerts
Click Settings (the gear icon) in the top navigation, then go to SMTP to configure outbound email for alerts. RamNode does not restrict outbound SMTP on KVM plans.
Recommended starting thresholds:
- CPU: alert when usage exceeds 90% for more than 5 minutes
- Memory: alert when usage exceeds 85%
- Disk: alert when usage exceeds 80%
- Status: alert immediately when the system goes offline
Add Remote Servers (Optional)
On the hub dashboard, click Add System and fill in the remote server's IP and a name. Beszel will display a Docker Compose snippet with the KEY and TOKEN pre-filled.
SSH into the remote server and create a minimal agent Compose file:
sudo mkdir -p /opt/beszel-agent
sudo nano /opt/beszel-agent/docker-compose.ymlservices:
beszel-agent:
image: henrygd/beszel-agent:latest
container_name: beszel-agent
restart: unless-stopped
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
PORT: "45876"
TOKEN: "your-token"
KEY: "ssh-ed25519 AAAA..."sudo ufw allow from YOUR_HUB_IP to any port 45876 proto tcp
sudo ufw reload
cd /opt/beszel-agent
sudo docker compose up -dEnable Automatic Backups (Optional)
Beszel stores all data in ./beszel_data. For automatic off-site backups, navigate to https://monitor.yourdomain.com/_/ (the PocketBase admin panel) and go to Settings → Backups.
Configure an S3-compatible bucket (Backblaze B2, Wasabi, AWS S3, or RamNode Object Storage). Set the schedule to daily and retain at least 7 copies.
Upgrading Beszel
cd /opt/beszel
sudo docker compose pull
sudo docker compose up -dDocker Compose will restart only the containers whose images changed. Data in ./beszel_data is preserved because it's mounted as a bind volume.
Troubleshooting
Dashboard loads but shows "disconnected" for the local agent
Check that the TOKEN and KEY in your Compose file exactly match what the hub generated. Even a single extra space will cause authentication to fail. Check agent logs with sudo docker logs beszel-agent.
Agents on remote servers cannot connect
Confirm port 45876 is open on the remote server's firewall from the hub IP. Also verify Docker is not blocking the port — run sudo iptables -L DOCKER -n on the remote server.
nginx returns a 502 Bad Gateway
The hub container may not be running. Check sudo docker ps and restart with sudo docker compose up -d beszel. Verify the port binding with sudo docker port beszel — it should show 127.0.0.1:8090.
Disk usage appears incorrect
If you have multiple mount points, Beszel may not pick them up automatically with Docker. Expose additional filesystems to the agent by adding read-only volume mounts under /extra-filesystems:
volumes:
- /mnt/data/.beszel:/extra-filesystems/data:ro