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.
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:
| Column | Meaning | What it tells you |
|---|---|---|
CPU% | The process's share of one core | On a 4-core server the figure goes up to 400%; 100% means one full core |
RES | Physical RAM actually in use | This is the right measure of each process's memory use |
VIRT | Reserved address space | Usually large and unimportant; don't worry about it |
S | Process state | R running, S normal sleep, D waiting on the disk |
TIME+ | Total CPU time consumed | Exposes 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.
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
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.
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.