← Back to writing

A Bash Script for a Quick Linux Server Health Check

Build a lightweight Bash health check that reports CPU, memory, disk, and the busiest processes using standard Linux tools.

When a Linux server feels slow, the first question is rarely “which monitoring platform should I install?” It is usually much simpler: what is consuming the machine right now?

For that first pass, a small shell script can be more useful than a dashboard. My linux-server-stats project combines tools already available on most Linux systems and reports five useful signals:

  • Total CPU usage
  • Free and used memory percentages
  • Free and used space on the root filesystem
  • The five processes using the most CPU
  • The five processes using the most memory

The result is intentionally small. It is not a replacement for historical monitoring, alerting, or distributed tracing. It is a fast diagnostic snapshot that can be run through an SSH session.

Building the script from standard tools

The script uses top, free, df, and ps. Each tool owns one part of the answer, while awk, sorting, and formatting turn their output into a compact report.

Calculating CPU usage

Running top in batch mode produces a single non-interactive snapshot:

top -bn1

The CPU row includes an idle percentage. The script subtracts that value from 100 to estimate current total usage:

top -bn1 | awk '/Cpu\(s\)/ {
  printf "Total CPU Usage: %.2f%%\n", 100 - $8
}'

This is a snapshot, so a short-lived spike can influence the result. For operational monitoring, take repeated samples or use a time-series agent. For an immediate check, however, it answers the right question with almost no setup.

Reading memory usage

free -m reports memory values in mebibytes. The Mem: row contains total, used, and free values, which can be converted into percentages:

free -m | awk '/Mem:/ {
  printf "Total Memory Usage (Free / Used): %.2f%% / %.2f%%\n",
    $4 * 100 / $2,
    $3 * 100 / $2
}'

Linux deliberately uses available memory for filesystem caching, so “free” and “available” are not identical concepts. A production-grade tool should normally highlight the available column as well. This small script stays faithful to its goal: a readable first diagnostic.

Checking the root filesystem

The root filesystem is a useful default because a full root volume can break package installs, logging, and application writes:

df -h /

Filtering the second row removes the header and lets the script present the capacity figures alongside CPU and memory.

Finding expensive processes

Aggregate percentages show that a resource is busy. A process list starts answering why.

ps -eo pid,comm,%cpu --sort=-%cpu | head -n 6
ps -eo pid,comm,%mem --sort=-%mem | head -n 6

The first row is the heading, so head -n 6 gives a label plus five processes. Sorting inside ps avoids needing a separate pipeline for this step.

Running the project

Clone the repository, enter it, make the script executable, and run it:

git clone https://github.com/Mohammed-Aftab-Siddique/linux-server-stats.git
cd linux-server-stats
chmod +x server-stats.sh
./server-stats.sh

Because the implementation reads system information without changing it, it does not need root access under normal Linux configurations.

What this teaches

The value of the exercise is not the number of lines. It demonstrates a useful Unix pattern: let focused tools produce structured text, then compose them into a workflow.

It also draws a clear boundary between diagnostics and monitoring. This script answers what is happening now. A monitoring system adds history, baselines, alert thresholds, dashboards, retention, and correlation across machines. The small diagnostic remains valuable even after those systems exist—especially when an agent is unavailable or you need to verify its story from the host itself.

Sensible next improvements

The script could grow without losing its simplicity:

  • Accept warning thresholds through command-line flags
  • Report load average and uptime
  • Include inode usage, not only disk capacity
  • Use MemAvailable for a clearer memory signal
  • Offer JSON output for automation
  • Return a non-zero exit code when a threshold is breached
  • Add tests using captured command output

The most important design choice is to keep the default output scannable. A server health check is useful when it reduces time-to-understanding, not when it reproduces every number the operating system can expose.

The complete implementation is available in the linux-server-stats repository.