Server management

Linux server monitoring commands: check CPU, RAM, disk and bandwidth

Linux server monitoring: terminal output of uptime and load average, CPU and RAM bars in htop, a disk-space table and a bandwidth graph, next to an automatic server alert message in Telegram

Your site has slowed down, your bot has stopped answering, or you simply have a feeling that something on the server is off. The first step is never a restart; it is looking. In this guide you will learn the Linux server monitoring commands that show exactly what your server's CPU, RAM, disk and bandwidth are doing, how to find the process hogging resources in a few seconds and, at the end, how to build a simple alert so that you hear about a problem before your users do.

Linux server monitoring commands: start with a 30-second uptime triage

Once you are logged in over SSH (if you are not set up yet, see our guide to connecting to a server over SSH), the first command is always this one:

uptime
# 14:32:10 up 42 days,  3:11,  1 user,  load average: 0.84, 1.02, 0.60

The last three numbers are the load average over the past 1, 5 and 15 minutes: the average number of processes that were either running or waiting in the queue to run (or waiting on the disk). On its own, this number means nothing; you have to measure it against the number of CPU cores:

nproc
# 4

The rule of thumb: as long as the load stays below the number of cores, the server is breathing easily. On a 4-core server, a load of 3 means busy but healthy; a load of 8 means two processes are queued for every core, and everything is crawling. Comparing the three numbers tells you the story, too: if the 1-minute figure is high but the 15-minute figure is low, the pressure has only just started; the reverse means a crisis is winding down.

💡 Tip: The Mehran Host panel shows CPU, RAM and bandwidth graphs for each server from the outside (from the hypervisor's point of view), which is ideal for finding out when the pressure hit. But which process was to blame can only be seen from inside the server itself; the two views complement each other rather than replace each other. One technical difference is worth knowing: the RAM graph is read from the hypervisor, and some guest operating systems do not report their memory use to the hypervisor at all. In that case the RAM graph stays empty, and free -h inside the server is your only reference.

RAM: why does free say memory is full?

This is where the most common misunderstanding in Linux monitoring lives. Run the command:

free -h
#                total   used    free   shared  buff/cache  available
# Mem:           3.8Gi   1.1Gi   210Mi  25Mi    2.5Gi       2.4Gi
# Swap:          1.0Gi   0B      1.0Gi

The free column shows only 210 MB, and yet the server is fine! Linux puts any RAM that would otherwise sit idle to work as a disk cache (the buff/cache column), so frequently used files are read from memory instead of from disk. That cache is released the moment a program asks for RAM. The number you really need to watch is the available column: memory that can be used right now. Here it is 2.4 GB, which means everything is perfectly normal.

A real RAM shortage shows up as two things: an available column close to zero, and constant use of Swap. The worst case is when the Linux kernel is forced to kill a process. If a program has disappeared "for no reason", look for the footprints of the OOM Killer:

journalctl -k | grep -i "out of memory"
# Out of memory: Killed process 1234 (mysqld) total-vm:2101960kB ...

The message Out of memory: Killed process means RAM really did run out: you either have to reduce usage or move to a bigger server plan.

htop: the server's control room

The classic top tool is available everywhere, but htop shows the same information in a more readable, interactive way. To install it:

# Ubuntu / Debian
sudo apt install htop
# AlmaLinux / Rocky
sudo dnf install htop

When you run htop, the top of the screen shows usage bars for each CPU core and for RAM, and below them sits the process table. The columns that matter:

ColumnMeaningWhat it tells you
CPU%The process's share of one coreOn a 4-core server the figure goes up to 400%; 100% means one full core
RESPhysical RAM actually in useThis is the right measure of each process's memory use
VIRTReserved address spaceUsually large and unimportant; don't worry about it
SProcess stateR running, S normal sleep, D waiting on the disk
TIME+Total CPU time consumedExposes a process that has been eating CPU for hours

A few shortcuts make htop ten times more useful: F6 to sort by any column (MEM%, for example), F5 for the tree view that shows which process is the child of which, F4 to filter by name, and F9 to kill the selected process.

💡 Tip: If several processes are stuck in state D (uninterruptible sleep) and the load is high while the CPU% figures are low, your bottleneck is not the CPU; it is the disk. Go straight to the iostat section of this article.

Find the resource-hungry process in one line

You don't always have to open htop; for a quick report (or for use in a script), these two lines are enough:

# Top 10 processes by RAM usage
ps aux --sort=-%mem | head -n 11

# Top 10 processes by CPU usage
ps aux --sort=-%cpu | head -n 11

The output shows the user, the PID and the usage percentages next to each process's full command line. This is usually where it becomes clear whether the culprit is a bloated mysqld, a PHP script stuck in an infinite loop, or a process you don't recognize at all (which is a security alarm in its own right; go through our Linux server security checklist).

Disk: space is one question, speed is another

Disk space with df and du

df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/vda1        40G   31G  7.2G  82% /

Once Use% passes 90%, it is time to find the culprit. Work your way down, directory by directory, with du:

sudo du -sh /var/* 2>/dev/null | sort -rh | head
# 12G   /var/log
# 8.5G  /var/lib/mysql
# ...

The usual suspects behind a full disk: unrotated logs in /var/log, old backups and, if you run Docker, unused images and volumes (check docker system df; our Docker guide covers this in detail).

Disk speed with iostat

Everyone notices a full disk; only iostat shows you a tired one. Install it from the sysstat package:

sudo apt install sysstat        # Ubuntu/Debian (dnf install sysstat on AlmaLinux)
iostat -x 2

You get a fresh report every 2 seconds. Two columns decide the verdict:

  • %util: the percentage of time the disk was busy. Figures that stay close to 100% mean the disk is saturated.
  • r_await / w_await: the average wait for each read or write operation, in milliseconds. On NVMe this should be under 1–2 ms; if you see double-digit numbers, either the disk is under heavy pressure or the provider hasn't given you real NVMe (we explain how to verify that in our article on KVM and NVMe).

Bandwidth: vnstat for history, iftop for right now

vnstat: your traffic accountant

vnstat is a lightweight service that records the traffic of each network interface by hour, day and month. It doesn't sniff packets; it only reads the kernel's counters, so it puts practically no load on the server:

sudo apt install vnstat
sudo systemctl enable --now vnstat

After a few hours, the data starts to build up:

vnstat -d       # daily report
#      day        rx      |     tx      |    total
#  2026-07-25   4.21 GiB  |  9.87 GiB  |  14.08 GiB
vnstat -m       # monthly totals: ideal for watching a traffic cap
vnstat -l       # live rate

iftop and ss: who is eating the bandwidth?

When the bandwidth graph suddenly shoots up, vnstat only tells you "how much"; for "who", turn to iftop:

sudo apt install iftop
sudo iftop -i eth0 -P

You get a live list of connections sorted by usage; the -P switch adds port numbers, so you can tell whether the traffic belongs to the web server, the database or something unexpected. For an exact list of which process is connected to where, and on which port:

ss -tunap | head -n 20
ss -s          # summary statistics for all connections
💡 Tip: Unusual outbound traffic (tx) and lots of connections to unknown IP addresses are classic signs of a hacked server that is being used to send spam or attack others. If you see this, save the output of ss -tunap before doing anything else, then move on to isolating the server.

Logs: take journalctl seriously

The numbers tell you that something is wrong; the logs tell you why. On every modern distribution (Ubuntu 22.04/24.04, AlmaLinux 9), systemd-journald keeps the logs of every service in one place:

# Errors from the current boot only
journalctl -p err -b

# Live log of one specific service
journalctl -u nginx -f

# Everything that happened in the past hour
journalctl --since "1 hour ago"

# How much disk space do the logs take up?
journalctl --disk-usage

If the last command reports several gigabytes, shrink the journal with sudo journalctl --vacuum-size=500M. A golden habit: every time a service restarts "for no reason" or you see a 502 Bad Gateway error, your first stop is journalctl -u SERVICE -n 50, not Google.

Automatic alerts: cron plus a Telegram message, no monitoring service needed

Real monitoring means you don't have to keep watching the server yourself. With a Telegram bot (BotFather creates one in two minutes; see our guide to running a Telegram bot on a VPS) and a ten-line script, the server warns you on its own. Create the file /root/watchdog.sh:

#!/bin/bash
TOKEN="123456789:AAExxxxxxxxxxxxxxxxxxx"   # bot token
CHAT="123456789"                            # your numeric chat ID
CORES=$(nproc)
LOAD=$(awk '{print $1}' /proc/loadavg)
MEM=$(free | awk '/Mem/ {printf "%d", $3/$2*100}')
DISK=$(df / --output=pcent | tail -1 | tr -dc '0-9')

MSG=""
awk -v l="$LOAD" -v c="$CORES" 'BEGIN {exit !(l > c*1.5)}' \
  && MSG="$MSG | Load: $LOAD ($CORES cores)"
[ "$MEM"  -gt 90 ] && MSG="$MSG | RAM: ${MEM}%"
[ "$DISK" -gt 90 ] && MSG="$MSG | Disk: ${DISK}%"

if [ -n "$MSG" ]; then
  curl -s "https://api.telegram.org/bot${TOKEN}/sendMessage" \
    -d chat_id="$CHAT" -d text="ALERT $(hostname)$MSG"
fi

Make it executable and schedule it in cron to run every 5 minutes:

chmod +x /root/watchdog.sh
crontab -e
# add this line:
*/5 * * * * /root/watchdog.sh >/dev/null 2>&1

The script's logic is simple: if the load climbs above 1.5 times the number of cores, or RAM or disk usage passes 90%, a message arrives; otherwise, nothing happens. Tune the thresholds to your own situation, and if you prefer another messenger, point the same curl at any API you like (the Iranian messenger Bale, a webhook, email); the logic stays the same.

One caveat before you rely on it: the alert only arrives if the server can reach api.telegram.org, and from a server inside Iran, where Telegram is blocked, it may not be able to. Run the same test as in the Telegram bot guide linked above once, from the server itself: curl -sS -m 10 -o /dev/null -w "%{http_code}\n" https://api.telegram.org. Any HTTP code (normally 302) means the path is open; a timeout means it is not, and the alert should go out through one of those other channels instead.

💡 Tip: Don't underestimate the disk alert: a disk that is 100% full corrupts MySQL and can even break SSH logins. A simple alert at 90% almost always leaves enough time to clean up, and, of course, regular backups are the insurance policy for all of this.

Lightweight graphical tools: btop and Netdata

btop: htop, only better-looking

btop belongs to the newer generation of terminal monitors: CPU and RAM history graphs plus disk and network lists, all on one screen and without any extra service. On Ubuntu 22.04 and later it is in the standard repositories:

sudo apt install btop   # (dnf install btop on AlmaLinux 9 with the EPEL repository)
btop

Netdata: a full web dashboard from one install script

If you want a browser dashboard with second-by-second graphs, Netdata installs with its kickstart script:

wget -O /tmp/netdata-kickstart.sh https://get.netdata.cloud/kickstart.sh
sh /tmp/netdata-kickstart.sh

It comes up on port 19999. But here is an important security warning: by default the dashboard listens on every interface, which means anyone on the internet can see full details of your server. Be sure to restrict it to localhost, in /etc/netdata/netdata.conf:

[web]
    bind to = 127.0.0.1

Then run sudo systemctl restart netdata, and to view the dashboard, open an SSH tunnel from your own computer:

ssh -L 19999:localhost:19999 root@SERVER_IP

Now open http://localhost:19999 in your own browser: a secure dashboard, without a single port open to the internet.

An honest word on the cost of monitoring itself: htop and btop only use resources while they are open, and vnstat reads counters the kernel already keeps, so all three are practically free. Netdata, on the other hand, is a permanent service that collects thousands of metrics every second and keeps them in memory. On small servers, btop for a live look plus vnstat for history and the alert script from the previous section get the job done without any extra service.

Next steps

Do three things today: install htop and vnstat, read free -h and df -h once with fresh eyes, and switch on the Telegram alert script. From then on, the server will call you before a crisis, not after one. If monitoring shows that you really are short on resources, plan the upgrade with our guide on how to choose a VPS; and if you are still not sure whether you need a VPS at all or whether shared hosting will do, our comparison of shared hosting and a VPS makes the decision simple. And if you don't have a server to practice on yet, create an hourly cloud server, try all of these commands on it for an hour or two, then delete it: hourly billing runs until the moment you delete the server, not until you power it off.

Frequently asked questions

What is the best Linux server monitoring tool?

There is no single best tool; the right one depends on the question you are asking. For a live view of CPU and RAM and to find the guilty process, use htop or btop; for traffic history, vnstat; for disk pressure, iostat from the sysstat package; and to find the cause, journalctl. If you want a web dashboard with second-by-second graphs, Netdata is the common choice. A practical setup usually has three parts: a live tool, a history recorder and an alerter.

Why does free show my server's RAM as full when the server isn't slow?

Because Linux turns idle RAM into disk cache to speed up file reads, and that cache is released as soon as a program asks for memory. In the output of free -h, look at the available column instead of the free column: that is the real measure, and buff/cache should be read as borrowed memory, not occupied memory. RAM is only a real problem when available is close to zero, Swap is constantly busy, or the kernel log shows "Out of memory: Killed process".

What load average number should worry you?

There is no fixed number; load has to be measured against the number of CPU cores. Load average is the average number of processes that were either running or waiting in the CPU and disk queues, so on a four-core server a load of 3 means busy but healthy, and 8 means two processes are queued for every core. Get the core count with nproc and read the 1-, 5- and 15-minute figures side by side to see whether the pressure is just starting or winding down.

How do I tell whether a slow server is short on CPU, RAM or disk?

Check three things in a row, one column at a time. If the CPU bars in htop are full and processes are in state R, the bottleneck is the CPU. If the available column in free -h is close to zero or Swap is constantly in use, RAM is short. If load is high but CPU percentages are low and several processes are stuck in state D, the disk is to blame; confirm with iostat -x 2, where %util near 100 and a double-digit await mean a saturated disk.

How can I find out which program is using my server's bandwidth?

Run three commands in order: vnstat for the totals, iftop for live connections and ss for the process behind each one. vnstat -d and vnstat -m show how much traffic went in and out each day and month, so you know whether a spike is real. sudo iftop -i eth0 -P lists live connections by usage, with port numbers. ss -tunap ties each connection to the name and PID of its owning process. Heavy outbound traffic to unknown IP addresses is a classic sign of a compromised server.

Related tutorials

Ready for some hands-on practice?

A Mehran Host hourly cloud server is ready in under 60 seconds. Practice as much as you like and pay only for the hours you use. Estimate the cost with the pricing calculator before you sign up.