Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

How to Monitor VPS Resources with Beszel: A Complete Guide

Published

monitoring
How to Monitor VPS Resources with Beszel

When a VPS slows down, a single top snapshot may miss the cause. Beszel records CPU, memory, disk, and container metrics so you can compare current activity with recent history. Its hub provides the dashboard; its agent collects measurements from the server.

This guide runs both components on one Ubuntu VPS using Docker. You will connect them, check the reported resources against the operating system, and verify that a short CPU workload appears in the charts. The dashboard stays private behind an SSH tunnel.

Before you start

Use an Ubuntu 24.04 VPS with a sudo-capable SSH account and enough free space for Docker images and monitoring history. This guide assumes a new Beszel installation and a standard rootful Docker daemon. It does not replace an existing Compose project or modify the SSH service. Replace SERVER_IP and ubuntu with your VPS address and login name. The $ prefix is a terminal prompt; do not copy it.

Keeping the hub on the monitored VPS is useful for resource history, but it cannot independently report the loss of that whole VPS: the dashboard goes down too. Use a separate monitoring host and an external notification path when outage alerting is the goal. This article verifies resource monitoring, not email delivery or off-server alerts.

If you only need an occasional live check, top, free, and df may be enough. Beszel is useful when you want saved charts and a container-by-container view. Docker socket access is powerful: a :ro mount does not turn the Docker API into a read-only API. Use the trusted agent image and keep dashboard access restricted.

Install Docker and start the hub

Run these commands on the VPS:

$ sudo apt-get update
$ sudo apt-get install -y docker.io docker-compose-v2 curl
$ sudo systemctl enable --now docker
$ sudo docker compose version
$ mkdir -p ~/beszel
$ chmod 700 ~/beszel
$ cd ~/beszel

Create ~/beszel/compose.yaml with these contents:

services:
  beszel:
    image: henrygd/beszel:0.19.0
    restart: unless-stopped
    environment:
      APP_URL: http://localhost:18090
    ports:
      - "127.0.0.1:8090:8090"
    volumes:
      - ./beszel_data:/beszel_data
      - ./beszel_socket:/beszel_socket

  beszel-agent:
    image: henrygd/beszel-agent:0.19.0
    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://127.0.0.1:8090
      KEY: "${BESZEL_KEY:-not-configured}"
      TOKEN: "${BESZEL_TOKEN:-not-configured}"

The hub binds only to the VPS loopback interface. The agent uses the host network to see host network statistics, but its inbound listener is a Unix socket, not a public TCP port. Both containers mount the same socket directory. HUB_URL also allows the agent to connect outward to the hub over a local WebSocket; the shared socket provides a second local connection path. The data directories retain the hub database and agent state outside the containers.

Validate the configuration, pull both pinned images, and start only the hub:

$ sudo docker compose config --quiet
$ sudo docker compose pull
$ sudo docker compose up -d beszel
$ sudo docker compose ps
$ curl --fail --silent --show-error http://127.0.0.1:8090/api/health

The recorded health response was:

{"message":"API is healthy.","code":200,"data":{}}

The hub should be running. Do not start the agent with the placeholder credentials; collect the real values in the next step.

Create the account and add the VPS

On your computer, open a terminal and leave this tunnel running:

$ ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:18090:127.0.0.1:8090 ubuntu@SERVER_IP

Open http://localhost:18090 in your browser. Create the first administrator account with your email address and a strong password. The server is accessible through SSH rather than an unencrypted public login page. Keep the tunnel open whenever you use the dashboard.

Choose Add System. Name it My VPS and enter this exact value in Host / IP:

/beszel_socket/beszel.sock

Use the public key and token from this system’s setup dialog for the agent. The public key identifies the hub to the agent; it is not your personal SSH key. Keep the token private. Do not paste the generated configuration into chat or a public repository.

On the VPS, from ~/beszel, create a private environment file before entering the values:

$ touch .env
$ chmod 600 .env

Edit ~/beszel/.env and replace both placeholders with the values from your hub. Preserve the complete public key, including its ssh-ed25519 prefix:

BESZEL_KEY='REPLACE_WITH_HUB_PUBLIC_KEY'
BESZEL_TOKEN='REPLACE_WITH_SYSTEM_TOKEN'

The single quotes keep the public key’s spaces intact. Do not copy example credentials from another installation. Keep the setup dialog open while you start the agent:

$ sudo docker compose config --quiet
$ sudo docker compose up -d
$ sudo docker compose ps

Finish with Add System in the browser. Wait for the system to show an online state, then open My VPS. Allow a few collection cycles for charts to accumulate; a newly added system has no earlier history.

Check what the metrics represent

On the VPS, collect an independent snapshot:

$ nproc
$ free -m
$ df -h /
$ sudo docker stats --no-stream

Compare the dashboard’s total memory and disk capacity with the server, then inspect its CPU, memory, disk, and container charts. Click Containers in the top navigation to open All Containers. The hub and agent should appear there because they are running Docker containers themselves. Compare container names with docker stats, not only the number of rows.

Do not expect every value to match to the last decimal place. The commands and Beszel sample at different times, and memory tools differ in how they account for cache. Compare units, overall capacity, and the direction of change. A filesystem’s free space is also different from its read/write throughput.

Check which filesystem the disk chart represents. On a simple VPS, it should correspond to the root disk. If Docker data lives on another filesystem, or the reported capacity differs from df -h /, inspect the Beszel disk configuration before trusting that chart. Do not assume every attached disk is monitored automatically.

Verify a change in CPU activity

If the VPS is busy or latency-sensitive, use an existing workload and watch its next activity period instead. On an otherwise idle test VPS, this bounded command keeps one CPU core busy for 90 seconds:

$ timeout 90s sh -c 'while :; do :; done'

The command normally prints nothing. It stops automatically; a timeout exit status of 124 is expected. Keep the Beszel system page open and watch the CPU chart during and after the run.

A single busy core is not necessarily 100% of the whole server: on a two-vCPU VPS, it contributes roughly half of total CPU capacity. In the two-vCPU test, Beszel recorded 50.26% CPU usage during the command and 0.18% after cooldown. Leave time for another collection cycle after the command ends and confirm that the chart falls again. A changing chart is stronger evidence of collection than an online badge alone.

If the system stays offline or charts are missing

  • Check the socket path: the Host / IP field must be /beszel_socket/beszel.sock, matching LISTEN and the shared mounts. localhost inside the hub container refers to that container, not automatically to the host-network agent.
  • Check credentials: use the current key and token from the same system dialog. After changing .env, run sudo docker compose up -d from ~/beszel to recreate the agent with the new environment. A plain container restart does not apply a changed Compose environment.
  • Check container visibility: the Docker socket must exist and be accessible to the agent. This example is for rootful Docker; rootless Docker and Podman use different socket paths.
  • Check the time range: a fresh system has limited history. Keep the recent range selected and wait for samples before interpreting an empty graph as failure. Sensors such as temperature or GPU usage may not exist on a VPS.

On the VPS, from ~/beszel, inspect the relevant logs:

$ sudo docker compose logs --tail=50 beszel
$ sudo docker compose logs --tail=50 beszel-agent

You now have private access to resource history for the VPS and its running containers. The test chart rose to 50.26% under a one-core load and returned to 0.18% afterward. That verifies resource collection, not independent whole-server outage detection.

Related guides

Account services are unavailable until the production backend is configured.