System Management with Bash: Monitoring Resources and Controlling Processes in DevOps
Comprehensive guide to monitoring CPU, RAM, and disk usage, managing processes, and controlling system services using Bash with ps, top, kill, systemctl, journalctl, df, free, lsof, and ss.
In the previous post, we explored integrating Bash into CI/CD pipelines—from enforcing Strict Mode to protecting secrets and preventing Command Injection. Now, we return to the server itself—where Bash demonstrates its most immediate power: system management and resource monitoring.
When operating DevOps infrastructure, you cannot always sit in front of a screen watching servers. Sometimes a process consumes RAM abnormally, a disk fills up and disrupts log rotation, or a critical service crashes in the middle of the night. In these moments, a well-crafted Bash script can detect issues before you even receive an emergency call, and even automatically intervene to restore service.
This article covers the core commands for monitoring system resources (CPU, RAM, Disk, Network), managing processes and services, and includes a practical DevOps script with cron integration for continuous server monitoring.
System Resource Monitoring
CPU and System Load
The top command is the first tool you reach for when inspecting CPU activity:
1
2
# Run top once and exit (suitable for scripts)top -bn1 | head -5
To extract the actual CPU usage percentage, use grep and awk:
Note on load average: If the load average exceeds the number of CPU cores, the system is overloaded. For example, a 4-core server with an average load of 5.2 indicates processes are waiting to be executed.
RAM and Swap
The free command displays memory status at a glance:
# Last 50 lines of Nginx logsjournalctl -u nginx -n 50 --no-pager
# Real-time log streaming (similar to tail -f)journalctl -u nginx -f
# Logs from 1 hour agojournalctl -u nginx --since "1 hour ago"
Checking Ports and Network Connections
ss (Socket Statistics) is the modern replacement for netstat:
1
2
3
4
5
6
7
8
# List all listening portsss -tlnp
# Check a specific port (e.g., 80)ss -tlnp | grep ':80'# Connection summary by statess -s
lsof helps find which process is using a file or port:
1
2
3
4
5
# Find the process holding port 443lsof -i :443
# Find which process has a specific file openlsof /var/log/syslog
Practical Example: System Monitoring and Auto-Remediation Script
Below is a complete script combining the commands above, designed to run periodically via cron, detect issues, and automatically intervene when necessary:
#!/usr/bin/env bash
# ==============================================================================# Script : system-monitor.sh# Purpose: Monitor system resources, alert, and auto-remediate# Schedule via cron: */5 * * * * /opt/scripts/system-monitor.sh# ==============================================================================set -euo pipefail
IFS=$'\n\t'readonlyLOG_FILE="/var/log/system-monitor.log"readonlyMAX_LOG_SIZE=10485760# 10MBreadonlyCPU_THRESHOLD="${CPU_THRESHOLD:-90}"readonlyRAM_THRESHOLD="${RAM_THRESHOLD:-85}"readonlyDISK_THRESHOLD="${DISK_THRESHOLD:-80}"log(){locallevel="$1";shiftecho"[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"| tee -a "$LOG_FILE" 2>/dev/null
# Rotate log if too largeif[[ -f "$LOG_FILE"]];thenlocal size
size=$(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE" 2>/dev/null ||echo 0)if[["$size" -gt "$MAX_LOG_SIZE"]];then mv "$LOG_FILE""${LOG_FILE}.1" touch "$LOG_FILE"fifi}# Check CPU — kill the top CPU consumer if threshold exceededcheck_cpu(){local cpu_idle cpu_usage top_pid
cpu_idle=$(top -bn1 | grep "Cpu(s)"| awk '{print $8}'| tr -d ','||echo"0")cpu_usage=$(echo"100 - $cpu_idle"| bc 2>/dev/null ||echo"0")if[["$(echo"$cpu_usage > $CPU_THRESHOLD"| bc 2>/dev/null ||echo 0)" -eq 1]];then log "WARN""CPU usage: ${cpu_usage}% (threshold: ${CPU_THRESHOLD}%)"# Get the highest CPU consuming PIDtop_pid=$(ps -eo pid,%cpu --sort=-%cpu | awk 'NR==2 {print $1}')if[[ -n "$top_pid"&&"$top_pid" !="PID"]];thenlocal top_cmd
top_cmd=$(ps -p "$top_pid" -o comm= 2>/dev/null ||echo"unknown") log "WARN""Killing PID $top_cmd ($top_pid) — high CPU"# Try SIGTERM first, wait 5s then SIGKILLkill"$top_pid" 2>/dev/null ||true sleep 5ifkill -0 "$top_pid" 2>/dev/null;thenkill -9 "$top_pid" 2>/dev/null ||true log "WARN""Force killed PID $top_pid"fifielse log "INFO""CPU: ${cpu_usage}% — OK"fi}# Check RAMcheck_ram(){local ram_pct
ram_pct=$(free | awk '/Mem:/ {printf "%.1f", $3/$2*100}')if[["$(echo"$ram_pct > $RAM_THRESHOLD"| bc 2>/dev/null ||echo 0)" -eq 1]];then log "WARN""RAM usage: ${ram_pct}% (threshold: ${RAM_THRESHOLD}%)"# Drop page cache (safe, no data loss) sync
echo3 > /proc/sys/vm/drop_caches 2>/dev/null ||true log "INFO""Dropped page cache"else log "INFO""RAM: ${ram_pct}% — OK"fi}# Check Diskcheck_disk(){whileread -r mount usage;dolocal pct
pct=$(echo"$usage"| tr -d '%')if[["$pct" -ge "$DISK_THRESHOLD"]];then log "WARN""Disk $mount usage: ${pct}% (threshold: ${DISK_THRESHOLD}%)"# Auto-clean compressed logs older than 30 days find /var/log -name "*.gz" -mtime +30 -delete 2>/dev/null ||true log "INFO""Cleaned old compressed logs (>30 days)"fidone < <(df -h --output=target,pcent 2>/dev/null | tail -n +2)}# Check critical servicescheck_services(){localcritical_services=("nginx""docker""sshd")for svc in "${critical_services[@]}";doif ! systemctl is-active --quiet "$svc" 2>/dev/null;then log "WARN""Service $svc is not running — attempting restart" systemctl restart "$svc" 2>/dev/null ||true sleep 2if systemctl is-active --quiet "$svc" 2>/dev/null;then log "INFO""Service $svc restarted successfully"else log "ERROR""Failed to restart service $svc"fifidone}# Check critical portscheck_ports(){localcritical_ports=(2280 443)for port in "${critical_ports[@]}";doif ! ss -tln | grep -q ":${port} " 2>/dev/null;then log "WARN""Port $port is not listening"fidone}main(){ log "INFO""=== System monitoring started ===" check_cpu
check_ram
check_disk
check_services
check_ports
log "INFO""=== System monitoring completed ==="}main "$@"
Cron Configuration for Periodic Execution
1
2
3
# Run every 5 minutescrontab -e
*/5 * * * * /opt/scripts/system-monitor.sh >> /var/log/system-monitor-cron.log 2>&1
Sample Output
1
2
3
4
5
6
7
8
9
[2026-08-29 10:00:01] [INFO] === System monitoring started ===
[2026-08-29 10:00:01] [INFO] CPU: 23.5% — OK
[2026-08-29 10:00:01] [INFO] RAM: 61.2% — OK
[2026-08-29 10:00:02] [WARN] Disk / usage: 87% (threshold: 80%)
[2026-08-29 10:00:02] [INFO] Cleaned old compressed logs (>30 days)
[2026-08-29 10:00:02] [INFO] Service nginx — running
[2026-08-29 10:00:02] [INFO] Service docker — running
[2026-08-29 10:00:02] [INFO] Service sshd — running
[2026-08-29 10:00:02] [INFO] === System monitoring completed ===
Deployment Notes & Best Practices
Avoid killing critical processes: Never kill -9 a process without fully identifying what it is. Always review ps aux output carefully before intervening. In the example script above, we only kill the highest CPU consumer—but in production, add a whitelist to protect important PIDs (databases, application servers).
Log rotation: The script auto-rotates logs when the file exceeds 10MB. In practice, integrate with logrotate for centralized log management and to retain necessary historical data.
Configurable thresholds: Use environment variables (CPU_THRESHOLD, RAM_THRESHOLD, DISK_THRESHOLD) instead of hardcoding values, so you can adjust per server without modifying the script.
Consider alternative solutions: Bash is well-suited for simple monitoring scripts. As your infrastructure grows (hundreds of servers), combine it with Prometheus + Grafana or dedicated monitoring tools—but Bash remains a fast choice for creating emergency intervention scripts.
Test before deployment: Always run scripts on staging or debug with bash -x before adding them to cron in production.
Conclusion
System management does not demand complex tools—often, a clean Bash script combining ps, top, free, df, systemctl, and ss is sufficient to keep servers running reliably. From monitoring CPU/RAM, checking disk usage, tracking service status, to automatically remediating detected issues, Bash remains a trustworthy companion for the DevOps Engineer.
In the next post, we will explore automating SSH with Bash: managing multiple servers simultaneously, executing remote commands, and transferring files securely with ssh, scp, and rsync.