SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Lightweight server monitoring tools for a VPS

How much monitoring does one VPS really need? Compare htop, Glances, Netdata, Beszel and Prometheus by what each costs to run, and when to move up.

How much monitoring does one VPS need?

Lightweight server monitoring tools come in three sizes, and one VPS almost never needs the biggest one. Size one is a live look inside an SSH session: htop or btop, running only while you watch. Size two is one always-on service that records history on the same box and draws it in a browser: Netdata or Beszel. Size three is Prometheus with node_exporter and Grafana, which is a platform built for a fleet. Start at size one. Add size two the first time you need to answer what happened at 03:00 while you were asleep.

Resource metrics are one job. Uptime, logs and disk health are others.

A CPU graph does not tell you the web server is returning 502. It does not tell you what the error said, and it does not tell you the disk is on the way out. Those are separate jobs with separate tools, and the fastest way to waste a weekend is to expect one dashboard to cover all of them.

An uptime check has to run somewhere else. A monitor living on the box cannot report that the box is gone, so external checks belong on another machine: an Uptime Kuma status page does that job. Log search is a second job, because log text does not fit in a metrics database, and a self-hosted log stack is where that belongs. Disk health is a third: a failing drive shows up in SMART (self-monitoring, analysis and reporting technology) attributes and in kernel I/O errors long before it shows up in a usage graph, which is the subject of SMART disk health monitoring on a VPS. One metric is easy to misread on shared hardware. High steal time means the hypervisor gave your vCPU to another guest, so your own graphs look idle while everything feels slow, and CPU steal time and noisy neighbours explains what to do about it.

Size one: read the box while you are logged in

sudo apt update
sudo apt install -y htop btop
htop

btop is packaged as btop on current Ubuntu and Debian, and it is licensed under Apache 2.0. Both tools read what the kernel already publishes in /proc, so nothing runs when you are not looking, and nothing is recorded. That is the trade: zero cost, zero history.

Two numbers explain most of what people call a slow server. The first is available memory. free -h looks alarming until you read the right column, because Linux spends free memory on page cache on purpose, so used climbs by design. The column that matters is available: what a new process can take without pushing anything to swap. The second is load average, which uptime prints for the last one, five and fifteen minutes. A load average above your core count means work is queued.

free -h
uptime
vmstat 1 5

vmstat 1 5 prints five samples one second apart. The first line is an average since boot, so read the lines after it. In the CPU columns, wa is time the processor spent waiting on disk, so a high wa with a low us means storage is your limit rather than the CPU. The st column is steal time, time your vCPU was ready to run and the host scheduled another guest instead.

Glances puts more on one screen, including per-process I/O and container stats. Install it with pipx so it gets its own virtual environment instead of fighting the system Python:

sudo apt install -y pipx
pipx install 'glances[all]'
glances

Glances 4 needs Python 3.10 or newer, which Ubuntu 24.04 has. If your shell answers glances: command not found immediately after a successful install, the binary is in ~/.local/bin and your current shell has no such entry in PATH. Run pipx ensurepath, then open a new login shell.

Size two: one always-on dashboard that keeps history on the box

The moment you want to know what the box was doing before you logged in, you need something that runs all the time and writes to disk. For a single VPS, this is where most setups should stop.

Netdata is one agent per machine, with its own dashboard and its own database on the same box, so nothing else has to exist. The documented installer downloads first and runs second, which is the point: you can read the script before it executes.

wget -O /tmp/netdata-kickstart.sh https://get.netdata.cloud/kickstart.sh
less /tmp/netdata-kickstart.sh
sh /tmp/netdata-kickstart.sh --stable-channel --disable-telemetry --non-interactive

--stable-channel takes stable releases instead of nightly builds. --disable-telemetry opts out of the anonymous statistics the installer otherwise sends. --non-interactive stops it asking questions, which matters when a script runs it. Trying Netdata costs one command to undo, because sh /tmp/netdata-kickstart.sh --uninstall removes the install again.

sudo systemctl status netdata --no-pager
curl -sI http://127.0.0.1:19999 | head -n 1

A healthy install answers HTTP/1.1 200 OK on port 19999, and systemctl status shows active (running).

Bind the dashboard to loopback before anyone else finds it

Netdata binds to every interface by default. On a VPS with a public IP address, that means port 19999 answers anyone your firewall lets through, and the dashboard names your hostname, your disks, your containers and every process on the machine. Fix it in the config:

cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
sudo ./edit-config netdata.conf
[web]
    bind to = 127.0.0.1
sudo systemctl restart netdata
ssh -N -L 19999:127.0.0.1:19999 you@your-server

-L forwards port 19999 on your laptop to port 19999 on the server's loopback address, and -N runs no remote command, so the terminal sits there quietly while the tunnel is open. Open http://127.0.0.1:19999 in your browser. If the page loads through the tunnel and a public address now refuses the connection, the bind change worked. To check the other direction, run curl -sI http://YOUR_PUBLIC_IP:19999 from your laptop before you make the change: a 200 OK there means the internet could read your dashboard.

Decide now which process dies when memory runs out

History costs disk, and an agent costs memory. On a 1 GB plan you want that decision made in advance rather than by the kernel at 03:00. A systemd drop-in caps the monitor:

sudo systemctl edit netdata
[Service]
MemoryMax=200M
CPUQuota=25%
sudo systemctl restart netdata
systemctl show netdata -p MemoryMax

The last command prints the limit that is actually live, which is the only proof the drop-in was picked up. With MemoryMax set, the kernel reclaims from that cgroup first and kills the service if it cannot reclaim enough, so the monitor dies instead of your application. Limiting process memory and CPU with systemd covers the rest of those properties.

Disk is the other half. Netdata keeps history in its own database on the box, sized by configuration rather than by a number of days, so check what your install is set to instead of assuming last week is still there.

df -h /
sudo du -xh --max-depth=1 /var | sort -h | tail -n 5

Run that a week after install, not on day one. A metrics database with no ceiling fills the same partition your application logs into, and the first symptom is usually the application, not the monitor.

Beszel, and the single binary shape

Beszel splits the work in two: a hub that serves the web interface, and an agent on each monitored machine. The hub is built on PocketBase and keeps its data in a directory you mount, the licence is MIT, and it reports CPU, memory, disk, network, temperatures, Docker container stats and SMART data. The hub listens on port 8090 by default and the agent on 45876. The agent authenticates with a public key plus a token, both shown by the hub when you add a system.

That split is useful on a small plan, because the hub does not have to live on the server you are watching. Put the hub on a home machine or a second box, and give the VPS only the agent:

curl -sL https://get.beszel.dev -o /tmp/install-agent.sh
chmod +x /tmp/install-agent.sh
/tmp/install-agent.sh

The script prompts for the public key if you do not pass -k, creates a beszel user, and installs a service so the agent comes back after a reboot. -p sets the port and -t passes the token. If you do run the hub in Docker on the same box, check that the container returns on its own after a restart, because a compose stack that starts on boot is the difference between a dashboard and a dashboard that worked until the last kernel update.

A third shape keeps appearing: one static Go binary, one systemd unit, one web page, no database server to run. vpsmon is a current example, a single binary that serves a web interface on port 8088 and installs from a script the README pipes into sudo bash. Download that script and read it before you run it, because piping a URL into a root shell hands the decision about what runs on your server to whoever controls that URL. Treat a footprint figure in any young project's README as the author's own measurement. The shape is sound and any individual project may be unmaintained in a year, so try one if you like it, and keep the tool you page yourself with boring.

What lightweight server monitoring tools cost to run

Count the moving parts before you count megabytes. Every service is a process to start at boot, a port to firewall, a config file to keep current, and one more thing that can fail while you are not watching.

ChartServices and listening ports each approach adds to one VPS
The data behind this chart
[
  {
    "tool": "htop or btop",
    "services_to_run": 0,
    "listening_ports": 0
  },
  {
    "tool": "Glances web mode",
    "services_to_run": 1,
    "listening_ports": 1
  },
  {
    "tool": "Netdata agent",
    "services_to_run": 1,
    "listening_ports": 1
  },
  {
    "tool": "Beszel hub and agent",
    "services_to_run": 2,
    "listening_ports": 2
  },
  {
    "tool": "Prometheus, node_exporter, Grafana",
    "services_to_run": 3,
    "listening_ports": 3
  }
]

A terminal tool adds 0 services and 0 listening ports, which is why it stays the right default for one machine. Netdata is 1 service and 1 port, with the database inside the same process. The stack on the last row is 3 services and 3 ports before you have written a single alert rule. Those counts are the honest comparison. Memory figures are not, because they depend on how many disks, containers and network interfaces the agent finds on your box.

Glances also runs as a web server with glances -w, which serves on port 61208. It is a live view in a browser rather than a history database, so use it when you want the terminal screen without the terminal, and use Netdata or Beszel when you want yesterday.

Size three: when Prometheus and Grafana start to pay

node_exporter on its own is cheap, and it is packaged: sudo apt install prometheus-node-exporter installs version 1.7.0-1ubuntu0.3 on Ubuntu 24.04 as of August 2026. The exporter is never the expensive part. Prometheus needs a server with a time series database that grows, Grafana needs another service with its own login, and both belong on a different machine from the one they watch. A metrics server that dies with the box it monitors cannot tell you why the box died.

The switch is about count, not sophistication. One server: size two, looked at directly. Three or four servers: still size two, one dashboard each, and a folder of bookmarks. Past that, hand-managed agents stop scaling, and a central server with templates and auto-discovery starts earning its keep. That is also where a Zabbix server on Ubuntu 24.04 becomes a reasonable answer, and where installing the agent from an Ansible playbook replaces logging into each box by hand.

Something has to wake you up

A dashboard nobody is looking at is not monitoring. Netdata ships alarms inside the agent, Beszel has configurable alerts in the hub, and both need somewhere to send them. Pushing to your phone through your own ntfy server keeps that path inside infrastructure you control. Set two alerts before you set twenty: disk above 85 percent, and the one service you actually care about no longer running. Alerts you ignore teach you to ignore alerts.

What breaks, and the message you will see

glances: command not found right after the install succeeded. pipx installs into ~/.local/bin. Ubuntu's default ~/.profile adds that directory to PATH only if it exists at login, so a shell you opened before the install never sees it. Run pipx ensurepath, log out, then log in again.

The Netdata page stopped loading after you edited netdata.conf. On the server, curl -sI http://127.0.0.1:19999 still answers HTTP/1.1 200 OK, while the public address refuses the connection. That is bind to = 127.0.0.1 doing exactly what you asked. Reach it through the SSH tunnel.

Beszel shows the system as down while the agent is running. Either the hub cannot reach the agent, or the key does not match. From the hub, run nc -vz your-server 45876 to see whether the port answers at all, then confirm the public key and token you pasted are the pair the hub generated for that system, not for a different one.

A service vanished under load. journalctl -u netdata -n 50 ends abruptly, and sudo dmesg -T | grep -i 'killed process' names what the kernel chose to kill. With no limits set, the kernel picks the biggest process, which on a small VPS is usually your application rather than the monitor. That is the argument for MemoryMax.

The disk filled up months later. Metrics databases grow. df -h / shows the partition, and sudo du -xh --max-depth=1 /var | sort -h | tail -n 5 shows which directory took the space, so you can cap the retention rather than delete files at random.

FAQ

Do I need Prometheus to monitor a single VPS?

No. Prometheus, node_exporter and Grafana are 3 separate services to install, secure and keep running, and for one machine they answer the same questions a single agent answers with one service and one port. Netdata or Beszel gives you history on the box itself. Move to Prometheus when you have several servers, want one place to see them, and are ready to run the metrics server somewhere other than the machines it watches.

Which uses less memory, Netdata or Glances?

Measure it on your own box instead of trusting a figure from a README, because the answer depends on how many disks, containers and interfaces the agent has to collect. systemctl status netdata prints a Memory: line for the unit, and systemd-cgtop -m orders running units by memory use, so you can watch both for a few minutes under real load. An idle box and a box running twenty containers give very different numbers for the same agent.

Is it safe to expose the monitoring dashboard on a public port?

Treat any monitoring interface as sensitive, because it lists hostnames, disk paths, container names and running processes. Netdata binds to all interfaces by default, so set bind to = 127.0.0.1 in the [web] section of netdata.conf and reach it over an SSH tunnel, or put it behind a reverse proxy that requires a login. The same applies to Glances in web mode on port 61208 and to any single-binary dashboard.

What will a metrics dashboard not tell me?

It cannot see whether your site answers from outside, what the error in the log actually said, or whether the disk is failing. External uptime checks run from another machine, log search needs a log store, and disk health comes from SMART attributes rather than from a usage graph. Use resource metrics for capacity questions, and keep those other jobs on tools built for them.