Beszel 0.21.0
Overview
This guide covers launching and using the Beszel 0.21.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened Zabbix & Infrastructure Monitoring AMIs listing on AWS Marketplace. You select it as the software version when you subscribe and launch.
Beszel is an open source, lightweight server and container monitoring hub. A clean web dashboard shows CPU, memory, disk, disk I/O, network, load, temperatures, systemd services and Docker container usage for every server you add. It keeps their history and sends alerts by email or through webhooks to Slack, Discord, Teams, Telegram, ntfy and many more. Small Beszel agents run on the servers you monitor, and the hub collects metrics from them.
The SecureAnyCloud image adds the following. What you get:
- HTTPS from the first boot. nginx serves Beszel on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- No first-visitor admin. Out of the box, Beszel offers a "create an admin account" form to whoever opens it first. This image never shows that form. First boot creates the administrator with a password unique to your instance while the hub is reachable only from the instance itself. The login is written to
/root/beszel-credentials.txt, and no password is baked into the image. - Already monitoring itself. A Beszel agent on the instance is paired at first boot with your hub's own key (generated on your instance), so the dashboard shows This hub with live metrics the first time you sign in.
- Fail closed. The hub refuses to start until first boot has created the administrator, nginx refuses to start until first boot has finished, and the local agent stays off until it has your hub's key.
- Loopback only. The hub listens on
127.0.0.1:8090and the local agent on127.0.0.1:45876. The only open ports are 22, 80 and 443. - Hardened defaults. The hub and the agent run as their own unprivileged users in systemd sandboxes. The hub, the agent and nginx are blocked from the instance metadata service, so a webhook URL or a system address cannot be pointed at it. The AMI requires IMDSv2. Requests for a host name that the instance does not serve are dropped, cross-site requests that would change your data are refused, sign-in attempts are rate-limited per client address, and the PocketBase superuser console is not exposed to the network.
- A dedicated data volume. The hub's database (users, systems, metrics history, alerts) and its key pair live on a separate 30 GiB EBS volume at
/var/lib/beszel. You can resize the volume on its own. - Patched base. Ubuntu 24.04 LTS, fully patched at build time, with unattended security upgrades enabled.
- A current Beszel release. 0.21.0 includes the fixes for the Beszel security advisories GHSA-759g-ch5m-2gch (fixed in 0.19.0: a signed-in user could subscribe to alerts on systems they do not own and receive their names and live metrics; 0.19.0 also blocks more internal addresses as notification URLs) and GHSA-wqvf-7262-m7g8 (fixed in 0.20.0: with OAuth self sign-up enabled, a new user could make themselves an administrator).
What is on the image: the Beszel hub and agent 0.21.0 (the official release binaries, checksum verified), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route that you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (the dashboard) and TCP 80 (redirects to HTTPS) from your own networks only. The hub needs no other inbound port, because it connects out to the agents on the servers you monitor (Step 7).
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Beszel 0.21.0 on Ubuntu 24.04 as the software version, choose your Region, then choose Launch through EC2.
Choose m5.large, your key pair and a security group as described above. Beszel is light, so a smaller instance is fine for a handful of servers. The AMI brings two volumes: the root volume and a 30 GiB data volume for the hub's database. Keep both.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=beszel}]'Connect to your instance
Version: Beszel 0.21.0 on Ubuntu 24.04.
SSH user: ubuntu.
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot takes a few seconds. After it finishes, the four units are active:
systemctl is-active beszel beszel-agent nginx beszel-firstbootactive active active activeThe hub and the local agent listen on loopback only, and nginx serves ports 80 and 443:
sudo ss -tlnp | awk 'NR==1 || /beszel|nginx|sshd/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'State Local Peer LISTEN 127.0.0.1:8090 users:(("beszel")) LISTEN 127.0.0.1:45876 users:(("beszel-agent")) LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:22 users:(("sshd"),("systemd")) LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:22 users:(("sshd"),("systemd"))beszel --version beszel-agent --version df -h /var/lib/beszel | tail -1beszel version 0.21.0 beszel-agent 0.21.0 /dev/nvme1n1 30G 924K 28G 1% /var/lib/beszelGet your URL and password
sudo cat /root/beszel-credentials.txtThe file holds the address that the hub is served as (your instance's public DNS name by default), the administrator email, the password generated on this instance, and your hub's public key. You give the public key to the agents on other servers (Step 7):
beszel.url=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ beszel.admin.user=admin@sac.local beszel.admin.pass=<24 letters and digits> beszel.hub.public_key=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA<generated on your instance>Sign in from your browser
Open beszel.url in your browser. The certificate is self-signed (generated on your instance), so accept the browser warning once, or install your own certificate (Step 11). Sign in with the email and password from Step 4.
The All Systems dashboard opens. This hub, the instance itself, is already listed and up, with its CPU, memory, disk, load, network, systemd service count and uptime.
Look at a system's history
Select This hub. The system page shows its details (address, host name, uptime, operating system, CPU model, memory) and charts for CPU usage, memory, disk usage, disk I/O, network traffic, load and more. Choose the time range at the top right, from one hour to 30 days.
Monitor your other servers
Each server that you want to monitor runs the Beszel agent. The hub connects to the agent on TCP port 45876. The agent accepts only a hub that holds the private key matching the public key it was given, so nothing else can read its metrics.
- In the dashboard, select Add System. Enter a name and the server's private IP address (or DNS name), and keep port 45876. The dialog shows your hub's public key and a one-time token.
- Choose the Binary tab (or Docker for a container) and copy the install command. Run it on the server you want to monitor. The command installs the agent as a service with your hub's key.
- On that server's security group, allow TCP 45876 inbound from the hub instance only (its security group or private IP).
- Back in the dialog, select Add System. The new system turns green within a minute.
Your hub's public key is also in /root/beszel-credentials.txt as beszel.hub.public_key, for configuration management. Agents can instead connect to the hub over HTTPS (the dialog's universal token option). For that option, first give the hub a DNS name and a certificate that the agents trust (Step 11).
Alerts and notifications
Select the bell icon on any system row to set alerts. You can alert on status (system down), CPU, memory, disk, bandwidth, temperature and load average, each with a threshold and duration.
Under Settings, Notifications, choose where alerts go. You can use email addresses (first configure an SMTP server in the superuser console, Step 10) and webhook or push URLs for Slack, Discord, Teams, Telegram, ntfy, Gotify, Pushover and other services that Beszel supports through Shoutrrr. The test button next to a URL sends a test message.
The hub cannot reach the instance metadata service, so a notification URL pointed at it fails instead of leaking the instance's credentials.
Security checks
Port 80 sends browsers to HTTPS, and the health endpoints answer without a password (for load balancer checks). Beszel's first-run form is closed. The superuser console, the superuser sign-in API and the account creation endpoint are refused from the network:
H=$(sudo sed -n 's#^beszel.url=https://\([^/]*\)/#\1#p' /root/beszel-credentials.txt)
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -w '\n' --resolve "$H:443:127.0.0.1" "https://$H/api/health"
curl -sk -w '\n' --resolve "$H:443:127.0.0.1" "https://$H/api/beszel/first-run" for p in /_/ /api/collections/_superusers/auth-with-password /api/beszel/create-user; do
curl -sk -o /dev/null -w "$p: %{http_code}\n" --resolve "$H:443:127.0.0.1" -X POST -H "Origin: https://$H" "https://$H$p" doneport 80: 301 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ {"message":"API is healthy.","code":200,"data":{}} {"firstRun":false} /_/: 403 /api/collections/_superusers/auth-with-password: 403 /api/beszel/create-user: 403A wrong password is refused, and your password signs you in as an administrator. The password is read from the credentials file and passed to curl on stdin, so it never appears on a command line:
sleep 4
H=$(sudo sed -n 's#^beszel.url=https://\([^/]*\)/#\1#p' /root/beszel-credentials.txt) login() { curl -sk -o /dev/null -w "$1: %{http_code}\n" --resolve "$H:443:127.0.0.1" -H "Origin: https://$H" \
-H 'Content-Type: application/json' --data-binary @- "https://$H/api/collections/users/auth-with-password"; } printf '{"identity":"admin@sac.local","password":"wrong-password-123"}' | login 'wrong password'
sudo sed -n 's/^beszel.admin.pass=\(.*\)$/{"identity":"admin@sac.local","password":"\1"}/p' /root/beszel-credentials.txt | login 'your password' sleep 4wrong password: 400 your password: 200The certificate was generated on this instance and names its own addresses:
H=$(sudo sed -n 's#^beszel.url=https://\([^/]*\)/#\1#p' /root/beszel-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltNamesubject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhostThe hub and the agent run unprivileged in sandboxes. Neither they nor nginx can reach the instance metadata service:
systemctl show beszel -p User -p ProtectSystem -p NoNewPrivileges -p IPAddressDeny
systemctl show beszel-agent -p User -p IPAddressDeny
systemctl show nginx -p IPAddressDenyIPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32 User=beszel ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32 User=beszel-agent IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32nginx passes the hub a fixed host name and the real client address from the connection, never values that a client sends. The hub's sign-in rate limit uses that address. nginx closes the connection for any host name that the instance does not serve:
grep -E 'proxy_set_header (Host|X-Forwarded-For|X-Real-IP)' /etc/nginx/sites-available/sac-beszel | sort -u curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: example.com' http://127.0.0.1/ || trueproxy_set_header Host 127.0.0.1:8090; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; unknown host name: 000000 means nginx closed the connection without an answer. nginx also refuses a change whose Origin is another website (HTTP 403), including a cross-site WebSocket connection.
The local agent holds your hub's public key, generated on this instance, and listens on loopback only:
sudo grep -E '^(LISTEN|KEY)=' /etc/beszel-agent/agent.env | sed -E 's/(AAAA[A-Za-z0-9+/]{4})[A-Za-z0-9+/=]+/\1.../'LISTEN=127.0.0.1:45876 KEY="ssh-ed25519 AAAAC3Nz..."The PocketBase superuser console
Beszel is built on PocketBase. Its superuser console (/_/) manages users, SMTP email settings, backups and logs. The image does not serve it to the network. Reach it through an SSH tunnel to the hub's loopback port. From your own computer, run:
ssh -i <your-key.pem> -L 8090:127.0.0.1:8090 ubuntu@<public-ip>Then open http://localhost:8090/_/ in your browser and sign in with the same email and password (Step 4). The administrator is also the PocketBase superuser. The links in Beszel's user menu that open the console (Users, Logs, Backups, Auth providers, SMTP settings) work only through this tunnel, at http://localhost:8090/.
To let colleagues sign in, add them in the console under Collections, users (role user or readonly). Then add them to the users field of each record in the systems collection that they may see.
Your own DNS name and certificate
Point a DNS record at the instance, then set the name that Beszel is served as. This command creates a new self-signed certificate for the name. It updates nginx, the hub's application URL (used in alert links and emails) and the URL in /root/beszel-credentials.txt, and it keeps the name across reboots:
sudo beszel-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running beszel-set-hostname again replaces it with a self-signed certificate:
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/beszel.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/beszel.key
sudo nginx -t && sudo systemctl reload nginxPassword, data and upgrades
beszel-set-password sets a new password for the administrator (both the Beszel sign-in and the PocketBase superuser) and records it in /root/beszel-credentials.txt. Run it without arguments for a new random password, or pass your own (24 to 64 letters and digits) on stdin:
sudo beszel-set-password printf '%s\n' '<your-new-password>' | sudo beszel-set-password --stdinThe hub's data directory on the data volume holds the database (data.db), the logs database (auxiliary.db) and the hub's key pair (id_ed25519, which the agents trust):
sudo ls /var/lib/beszel/pb_dataauxiliary.db auxiliary.db-shm auxiliary.db-wal data.db data.db-shm data.db-wal id_ed25519Back up the data with EBS snapshots of the data volume, or with the backups page of the superuser console (Step 10). Keep id_ed25519 with the database, because a new key means re-pairing every agent. To grow the volume, modify it in the EC2 console, then run sudo resize2fs on its device (findmnt -no SOURCE /var/lib/beszel).
The hub and the agent are single binaries in /usr/local/bin. To move to a newer release later, snapshot the instance. Then replace the binaries with the matching release's beszel and beszel-agent from the Beszel GitHub releases, and run sudo systemctl restart beszel beszel-agent. The image ships the version named in the listing.
Server components
Component Path
Beszel hub /usr/local/bin/beszel (beszel.service, user beszel, 127.0.0.1:8090)
Beszel agent for this host /usr/local/bin/beszel-agent (beszel-agent.service, user beszel-agent, 127.0.0.1:45876)
Hub data (database, key pair) /var/lib/beszel/pb_data on the 30 GiB data volume
Local agent settings /etc/beszel-agent/agent.env
nginx site /etc/nginx/sites-available/sac-beszel
Certificate and key /etc/nginx/ssl/
First boot script /usr/local/sbin/beszel-firstboot.sh
Host name and password helpers /usr/local/sbin/beszel-set-hostname, /usr/local/sbin/beszel-set-password
Credentials /root/beszel-credentials.txt (mode 0600)
Security recommendations
- Allow ports 22, 80 and 443 in the security group from your own networks only, and allow port 45876 on monitored servers from the hub only.
- Keep the administrator password secret, and rotate it if it may have been shared (Step 12).
- Use your own DNS name and a certificate that your browsers trust (Step 11).
- Keep the operating system patched. Unattended security upgrades are enabled, and
sudo apt-get update && sudo apt-get upgradeapplies everything else. - Snapshot the data volume regularly (Step 12).
Support and licensing
Beszel is open source under the MIT license (a copy is at /usr/share/doc/beszel/LICENSE). There is no per-server or per-CPU license fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on AWS
Launch Beszel 0.21.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com