How to Automate Daily Linux Health Checks with a Bash Script + Cron

Introduction
Every Linux server runs silently in the background, accumulating usage, filling disks, crashing services, and building up memory pressure — often for hours or days before anyone notices. By the time users report a problem, the damage is already done: a full disk has corrupted a database write, a failed service has taken down a critical API endpoint, or a memory leak has brought a production web server to its knees. The reactive approach to system administration is not just stressful — it is expensive.
The professional alternative is proactive monitoring: checking system health automatically, on a schedule, and receiving reports that tell you what is happening before it becomes a problem. In a well-administered Linux environment, the sysadmin should always know the disk usage trend before it hits 100%, the CPU load pattern before it sustains above 90%, and which services have failed before users file a ticket.
This guide shows you how to build a complete, production-ready Bash script that performs daily Linux system health checks automatically and delivers results to a log file and optionally to your inbox. We cover every check the script performs, explain the Linux commands behind each one, walk through setting up a cron job for scheduled execution, and add a bonus MOTD (Message of the Day) configuration that displays key health metrics every time you SSH into the server. The script works on Ubuntu, Debian, RHEL, CentOS, Rocky Linux, Fedora, and any other major Linux distribution.
What the Health Check Script Monitors
The script performs eight distinct health checks, each targeting a different layer of the Linux system stack. Here is what each check does and why it matters:
| Health Check | What It Reveals | Primary Command |
| Disk Usage | Filesystem utilization across all mounted partitions — catches full or near-full disks before they cause failures | df -h |
| CPU Load Average | 1, 5, and 15-minute load averages — sustained high load indicates CPU saturation or runaway processes | uptime |
| Memory Usage | Total, used, free, and available RAM and swap — identifies memory pressure, leaks, and swap saturation | free -h |
| Failed Systemd Services | Services in a failed state — catches silent crashes of web servers, databases, and background daemons | systemctl –failed |
| Top CPU Processes | The 5 processes consuming the most CPU — identifies runaway processes, infinite loops, and compute hogs | ps –sort=-%cpu |
| Top Memory Processes | The 5 processes consuming the most RAM — identifies memory leaks, oversized applications, and zombie accumulation | ps –sort=-%mem |
| System Uptime | How long the system has been running — reveals unexpected reboots, kernel panics, and power events | uptime -p |
Step 1: Create the Health Check Script
Setting Up the Script Directory and File
Create a dedicated directory for your automation scripts and then create the health check script file. Using /opt/scripts as the base directory keeps automation scripts separate from system binaries and user home directories, making them easy to find and manage:
# Create the scripts directory
sudo mkdir -p /opt/scripts
# Create and open the health check script
sudo nano /opt/scripts/system-health.sh
The Complete Health Check Script
Copy the following complete script into the editor. We will break down each section in detail after presenting the full script:
#!/bin/bash
# =============================================================
# System Health Check Script — vmorecloud.com
# Description: Daily automated health report for Linux systems
# Tested on: Ubuntu, Debian, RHEL, CentOS, Rocky Linux, Fedora
# =============================================================
LOGFILE="/var/log/system-health-$(date +%F).log"
HOSTNAME=$(hostname)
DATE=$(date)
{
echo "============================================="
echo " System Health Report for $HOSTNAME"
echo " Generated on: $DATE"
echo "============================================="
# ── 1. Disk Usage ──────────────────────────────────────────
echo -e "\nDisk Usage:"
printf "%-20s %-10s %-10s %-10s %-6s %-s\n" \
"Filesystem" "Size" "Used" "Avail" "Use%" "Mounted on"
df -h --output=source,size,used,avail,pcent,target | tail -n +2
# ── 2. CPU Load Average ────────────────────────────────────
echo -e "\nCPU Load (1/5/15 min average):"
uptime | awk -F'load average:' '{ print " " $2 }'
# ── 3. Memory Usage ────────────────────────────────────────
echo -e "\nMemory Usage:"
free -h | awk 'NR==1 || /Mem|Swap/ { printf " %-10s %-10s %-10s %-10s %-10s %-10s\n", \
$1, $2, $3, $4, $5, $6 }'
# ── 4. Failed Systemd Services ─────────────────────────────
echo -e "\nFailed Systemd Services:"
FAILED=$(systemctl --failed --no-legend)
if [ -z "$FAILED" ]; then
echo " No failed services. All systems operational."
else
echo "$FAILED" | while read -r line; do
echo " [FAILED] $line"
done
fi
# ── 5. Top 5 Memory-Consuming Processes ────────────────────
echo -e "\nTop 5 Memory Consuming Processes:"
ps -eo user,pid,%cpu,%mem,command --sort=-%mem | head -n 6 | \
awk '{ printf " %-10s %-6s %-6s %-6s %-s\n", $1, $2, $3, $4, \
substr($0, index($0,$5), 60) }'
# ── 6. Top 5 CPU-Consuming Processes ───────────────────────
echo -e "\nTop 5 CPU Consuming Processes:"
ps -eo user,pid,%cpu,%mem,command --sort=-%cpu | head -n 6 | \
awk '{ printf " %-10s %-6s %-6s %-6s %-s\n", $1, $2, $3, $4, \
substr($0, index($0,$5), 60) }'
# ── 7. System Uptime ───────────────────────────────────────
echo -e "\nSystem Uptime:"
echo " $(uptime -p)"
# ── 8. Available Package Updates ───────────────────────────
echo -e "\nAvailable Package Updates:"
if command -v apt &> /dev/null; then
UPDATES=$(apt list --upgradable 2>/dev/null | grep -v "Listing...")
if [ -z "$UPDATES" ]; then
echo " System is up to date."
else
echo "$UPDATES" | awk '{ print " " $0 }'
fi
elif command -v dnf &> /dev/null; then
dnf check-update 2>/dev/null || echo " System is up to date."
elif command -v yum &> /dev/null; then
yum check-update 2>/dev/null || echo " System is up to date."
elif command -v checkupdates &> /dev/null; then
checkupdates 2>/dev/null || echo " System is up to date."
else
echo " Package manager not detected."
fi
echo -e "\n===== End of Health Report ====="
} > "$LOGFILE"
# ── Email Report (optional — requires mail configured) ──────
MAIL_TO="admin@yourdomain.com" # Change to your email address
if command -v mail > /dev/null 2>&1; then
mail -s "Daily Health Report: $HOSTNAME ($(date +%F))" \
"$MAIL_TO" < "$LOGFILE"
fi
Press Ctrl+O then Enter to save, and Ctrl+X to exit nano. Then make the script executable:
sudo chmod +x /opt/scripts/system-health.sh
Understanding Each Health Check: Commands Explained
Script Variables and Log File Setup
The script begins by defining three variables and a log file path:
LOGFILE="/var/log/system-health-$(date +%F).log"
HOSTNAME=$(hostname)
DATE=$(date)
The LOGFILE variable uses command substitution with date +%F to embed today’s date in ISO 8601 format (YYYY-MM-DD) directly into the filename. This means each day’s report gets its own uniquely named log file — system-health-2026-04-05.log — and reports never overwrite each other. After 30 days, you have a full month of daily health snapshots in /var/log/ that you can diff, grep, or analyze for trends.
The curly braces { } that wrap the entire report section redirect all output from every command inside them to the log file in a single operation. Everything between { and } > “$LOGFILE” gets written to that day’s log file rather than the terminal — a clean pattern that is significantly more maintainable than appending >> to every individual echo statement.
| TROUBLESHOOTING | If a section is blank or shows an error, run the individual command outside the script to debug it. For example, if the package update section is blank, run: apt list –upgradable 2>/dev/null | grep -v ‘Listing…’ — manually and check the output. Most issues are caused by command availability or permission differences when running as root. |
Step 3: Automate with Cron — Daily Scheduled Execution
Understanding Cron Scheduling
Cron is the built-in Linux task scheduler that runs commands at specified times. A crontab entry has five time fields followed by the command to run. The format is:
# ┌───────────── Minute (0–59)
# │ ┌─────────── Hour (0–23)
# │ │ ┌───────── Day of Month (1–31)
# │ │ │ ┌─────── Month (1–12)
# │ │ │ │ ┌───── Day of Week (0–7, Sunday = 0 or 7)
# │ │ │ │ │
# * * * * * command
An asterisk (*) in any position means ‘every’ — every minute, every hour, every day. To run the health check at 7:00 AM every day, use 0 7 * * * (minute 0, hour 7, every day of month, every month, every day of week).
Adding the Cron Job
Since the health check script accesses system-level resources (reading all processes, checking systemd units, querying package managers), it should run as root. Open the root user’s crontab:
sudo crontab -e
Add the following line at the bottom of the file to run the health check at 7:00 AM every morning:
0 7 * * * /opt/scripts/system-health.sh
For a more robust cron entry that also captures any script errors or cron execution issues to a separate log:
0 7 * * * /opt/scripts/system-health.sh >> /var/log/system-health-cron.log 2>&1
Save and exit the editor. Cron will automatically detect the change and schedule the job. Verify the cron job was saved correctly:
sudo crontab -l
Your cron entry should appear in the output, confirming it is scheduled.
| SCHEDULING TIPS | Run the health check early enough to be actionable — 7 AM gives you the report before the workday begins. If you manage servers across time zones, adjust the cron time to match your workday start. Consider also adding a weekly summary run at midnight Sunday: 0 0 * * 0 — to generate a weekend wrap-up report you can review Monday morning. |
Step 2: Test the Script Before Automating
Run the Script Manually and Verify the Output
Always test a script manually before automating it with cron. If there is a bug, you want to discover it now rather than when cron runs it silently at 7 AM and generates no useful output. Run the script with root privileges:
sudo /opt/scripts/system-health.sh
The script does not produce terminal output — all output is redirected to the log file. Verify the log file was created and review its contents:
cat /var/log/system-health-$(date +%F).log
You should see a well-structured report with all eight sections populated with data from your system. Review each section carefully:
- Disk Usage: All mounted filesystems should appear with size, used, available, and percentage columns aligned.
- CPU Load: Three load average values should appear on a single line.
- Memory: Both Mem: and Swap: rows should appear with numeric values in human-readable format.
- Failed Services: Either ‘No failed services’ or a list of failed units with their state.
- Top Processes: Two tables of 5 processes each, sorted by memory then CPU, with user, PID, and command visible.
- Uptime: A human-readable duration like ‘up 3 days, 2 hours’.
- Package Updates: Either ‘System is up to date’ or a list of available package upgrades.
Conclusion
Automating daily Linux system health checks is one of the highest-return investments in system administration. The 15-minute setup pays back in hours of proactive problem detection over the life of every server it is deployed on. The script in this guide — disk usage, CPU load, memory, failed services, top processes, uptime, and package updates — covers every major health dimension that experienced sysadmins monitor daily, packaged into a single automated report delivered to a log file and optionally to your inbox every morning.
The cron + mail combination is intentionally simple. There are more sophisticated monitoring stacks — Prometheus with Alertmanager, Nagios, Zabbix, Grafana Loki for log aggregation — but they require significant infrastructure investment and ongoing maintenance. The bash + cron approach works on every Linux server with no additional software, no agents to maintain, and no dashboards to manage. For personal VPS setups, home labs, and small production environments, it provides exactly the coverage you need with exactly zero operational overhead after the initial setup.







