Monitoreo de recursos del servidor con Bash: CPU, RAM y disco
Un servidor en producción sin monitoreo es como manejar un auto sin tablero: puedes ir a 200 km/h sin saberlo, quedarte sin combustible en medio de la ruta o reventar el motor sin que te avise nada. Las herramientas de monitoreo como Grafana, Prometheus o Datadog son excelentes, pero para un VPS chico o un proyecto personal, armar tu propio script de monitoreo en Bash es más que suficiente y te da un control total sobre qué métricas ver y cómo verlas.
En este post vamos a construir un script que te muestra el estado completo de tu servidor en una sola ejecución: CPU, RAM, disco, los procesos que más consumen y alertas automáticas cuando algo está por explotar. Todo en texto plano, sin dependencias raras, listo para correr a mano o programar con cron.
Qué tiene que reportar el script
Antes de escribir código, definamos qué información necesitamos. Esto es lo que yo reviso cada vez que entro a un servidor:
- Información general del sistema: hostname, kernel, uptime y cantidad de usuarios conectados.
- Uso de CPU: load average, porcentaje de uso por núcleo y los 5 procesos que más CPU consumen.
- Uso de memoria RAM: total, usada, libre, disponible y los 5 procesos que más RAM consumen.
- Uso de disco: espacio total, usado y disponible por partición, más los directorios que más ocupan.
- Alertas automáticas: si algún recurso supera un umbral configurable, el script lo marca claramente.
- Todo en un formato legible, con barras de progreso en ASCII y colores cuando la terminal lo soporta.
Con eso definido, vamos al script.
El script completo
Acá está la versión final. Después de la versión completa explico cada bloque en detalle.
#!/bin/bash
#
# monitor.sh - Monitoreo de recursos del servidor
# Uso: ./monitor.sh
# Crontab sugerido: */5 * * * * /opt/scripts/monitor.sh --alert-only >> /var/log/monitor.log
#
set -euo pipefail
# ============================================
# CONFIGURACIÓN - UMBRALES DE ALERTA
# ============================================
CPU_ALERT_THRESHOLD=80
RAM_ALERT_THRESHOLD=80
DISK_ALERT_THRESHOLD=85
TOP_N=5
# ============================================
# COLORES (se desactivan si no hay terminal)
# ============================================
if [[ -t 1 ]]; then
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
else
RED=''
YELLOW=''
GREEN=''
CYAN=''
BOLD=''
RESET=''
fi
# ============================================
# FUNCIONES AUXILIARES
# ============================================
separator() {
echo -e "${CYAN}$(printf '=%.0f' {1..70})${RESET}"
}
header() {
echo ""
separator
echo -e "${BOLD} $1${RESET}"
separator
}
alert_check() {
local value=$1
local threshold=$2
local label=$3
if (( value >= threshold )); then
echo -e " ${RED}⚠ ALERTA: $label al ${value}% (umbral: ${threshold}%)${RESET}"
else
echo -e " ${GREEN}✓ $label: ${value}% (OK)${RESET}"
fi
}
progress_bar() {
local percent=$1
local width=40
local filled=$(( percent * width / 100 ))
local empty=$(( width - filled ))
local color="$GREEN"
if (( percent >= 80 )); then
color="$RED"
elif (( percent >= 60 )); then
color="$YELLOW"
fi
printf " ${color}["
printf '#%.0f' $(seq 1 $filled 2>/dev/null) || true
printf '-%.0f' $(seq 1 $empty 2>/dev/null) || true
printf "] %3d%%${RESET}\n" "$percent"
}
# ============================================
# MODO SOLO ALERTAS (para cron)
# ============================================
if [[ "${1:-}" == "--alert-only" ]]; then
ALERTS=0
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{printf "%d", $2 + $4}')
RAM_PERCENT=$(free | awk '/^Mem:/ {printf "%d", $3/$2 * 100}')
while IFS= read -r line; do
DISK_PERCENT=$(echo "$line" | awk '{gsub(/%/,""); print $5}')
DISK_MOUNT=$(echo "$line" | awk '{print $6}')
if (( DISK_PERCENT >= DISK_ALERT_THRESHOLD )); then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ALERTA DISCO: $DISK_MOUNT al ${DISK_PERCENT}%"
ALERTS=$((ALERTS + 1))
fi
done < <(df -h --output=pcent,target -x tmpfs -x devtmpfs -x squashfs 2>/dev/null | tail -n +2)
if (( CPU_USAGE >= CPU_ALERT_THRESHOLD )); then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ALERTA CPU: ${CPU_USAGE}%"
ALERTS=$((ALERTS + 1))
fi
if (( RAM_PERCENT >= RAM_ALERT_THRESHOLD )); then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ALERTA RAM: ${RAM_PERCENT}%"
ALERTS=$((ALERTS + 1))
fi
if (( ALERTS == 0 )); then
exit 0
fi
exit 0
fi
# ============================================
# REPORTE COMPLETO
# ============================================
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════════╗${RESET}"
echo -e "${BOLD}║ REPORTE DE RECURSOS DEL SERVIDOR ║${RESET}"
echo -e "${BOLD}║ $(date '+%Y-%m-%d %H:%M:%S') ║${RESET}"
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════════╝${RESET}"
# ------------------------------------------
# INFORMACIÓN DEL SISTEMA
# ------------------------------------------
header "INFORMACIÓN DEL SISTEMA"
echo -e " ${BOLD}Hostname:${RESET} $(hostname)"
echo -e " ${BOLD}Kernel:${RESET} $(uname -r)"
echo -e " ${BOLD}SO:${RESET} $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2 || echo 'Desconocido')"
echo -e " ${BOLD}Uptime:${RESET} $(uptime -p 2>/dev/null || uptime | sed 's/.*up/up/')"
echo -e " ${BOLD}Usuarios:${RESET} $(who | wc -l) conectados"
# ------------------------------------------
# USO DE CPU
# ------------------------------------------
header "USO DE CPU"
LOAD_AVG=$(cat /proc/loadavg)
LOAD_1=$(echo "$LOAD_AVG" | awk '{print $1}')
LOAD_5=$(echo "$LOAD_AVG" | awk '{print $2}')
LOAD_15=$(echo "$LOAD_AVG" | awk '{print $3}')
CPU_CORES=$(nproc)
echo -e " ${BOLD}Núcleos:${RESET} $CPU_CORES"
echo -e " ${BOLD}Load Average:${RESET} $LOAD_1 (1min) | $LOAD_5 (5min) | $LOAD_15 (15min)"
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{printf "%d", $2 + $4}')
echo -e " ${BOLD}Uso total:${RESET} ${CPU_USAGE}%"
progress_bar "$CPU_USAGE"
alert_check "$CPU_USAGE" "$CPU_ALERT_THRESHOLD" "CPU"
echo ""
echo -e " ${BOLD}Top $TOP_N procesos por CPU:${RESET}"
echo -e " ${CYAN}$(printf '%-8s %-6s %-6s %s' 'PID' '%CPU' '%MEM' 'COMANDO')${RESET}"
ps aux --sort=-%cpu | head -n $((TOP_N + 1)) | tail -n $TOP_N | while read -r line; do
PID=$(echo "$line" | awk '{print $2}')
CPU=$(echo "$line" | awk '{printf "%.1f", $3}')
MEM=$(echo "$line" | awk '{printf "%.1f", $4}')
CMD=$(echo "$line" | awk '{for(i=11;i<=NF;i++) printf "%s ", $i; print ""}' | xargs)
printf " %-8s %-6s %-6s %s\n" "$PID" "$CPU" "$MEM" "$CMD"
done
# ------------------------------------------
# USO DE MEMORIA RAM
# ------------------------------------------
header "USO DE MEMORIA RAM"
RAM_INFO=$(free -h)
RAM_TOTAL=$(echo "$RAM_INFO" | awk '/^Mem:/ {print $2}')
RAM_USED=$(echo "$RAM_INFO" | awk '/^Mem:/ {print $3}')
RAM_FREE=$(echo "$RAM_INFO" | awk '/^Mem:/ {print $4}')
RAM_AVAILABLE=$(echo "$RAM_INFO" | awk '/^Mem:/ {print $7}')
RAM_PERCENT=$(free | awk '/^Mem:/ {printf "%d", $3/$2 * 100}')
SWAP_TOTAL=$(echo "$RAM_INFO" | awk '/^Swap:/ {print $2}')
SWAP_USED=$(echo "$RAM_INFO" | awk '/^Swap:/ {print $3}')
echo -e " ${BOLD}Total:${RESET} $RAM_TOTAL"
echo -e " ${BOLD}Usada:${RESET} $RAM_USED"
echo -e " ${BOLD}Libre:${RESET} $RAM_FREE"
echo -e " ${BOLD}Disponible:${RESET} $RAM_AVAILABLE"
echo -e " ${BOLD}Uso:${RESET} ${RAM_PERCENT}%"
progress_bar "$RAM_PERCENT"
alert_check "$RAM_PERCENT" "$RAM_ALERT_THRESHOLD" "RAM"
echo ""
echo -e " ${BOLD}Swap:${RESET} $SWAP_USED / $SWAP_TOTAL"
echo ""
echo -e " ${BOLD}Top $TOP_N procesos por RAM:${RESET}"
echo -e " ${CYAN}$(printf '%-8s %-6s %-6s %s' 'PID' '%MEM' '%CPU' 'COMANDO')${RESET}"
ps aux --sort=-%mem | head -n $((TOP_N + 1)) | tail -n $TOP_N | while read -r line; do
PID=$(echo "$line" | awk '{print $2}')
CPU=$(echo "$line" | awk '{printf "%.1f", $3}')
MEM=$(echo "$line" | awk '{printf "%.1f", $4}')
CMD=$(echo "$line" | awk '{for(i=11;i<=NF;i++) printf "%s ", $i; print ""}' | xargs)
printf " %-8s %-6s %-6s %s\n" "$PID" "$MEM" "$CPU" "$CMD"
done
# ------------------------------------------
# USO DE DISCO
# ------------------------------------------
header "USO DE DISCO"
echo -e " ${BOLD}Particiones:${RESET}"
echo -e " ${CYAN}$(printf '%-20s %-8s %-8s %-8s %-6s %s' 'FILESYSTEM' 'TOTAL' 'USADO' 'LIBRE' '%USO' 'MOUNT')${RESET}"
df -h --output=source,size,used,avail,pcent,target -x tmpfs -x devtmpfs -x squashfs 2>/dev/null | tail -n +2 | while read -r line; do
FS=$(echo "$line" | awk '{print $1}')
SIZE=$(echo "$line" | awk '{print $2}')
USED=$(echo "$line" | awk '{print $3}')
AVAIL=$(echo "$line" | awk '{print $4}')
PCT=$(echo "$line" | awk '{gsub(/%/,""); print $5}')
MOUNT=$(echo "$line" | awk '{print $6}')
local_color="$GREEN"
if (( PCT >= DISK_ALERT_THRESHOLD )); then
local_color="$RED"
elif (( PCT >= 60 )); then
local_color="$YELLOW"
fi
printf " %-20s %-8s %-8s %-8s ${local_color}%-5s%%${RESET} %s\n" "$FS" "$SIZE" "$USED" "$AVAIL" "$PCT" "$MOUNT"
done
echo ""
echo -e " ${BOLD}Top 10 directorios más pesados en /:${RESET}"
du -h --max-depth=2 / 2>/dev/null | sort -rh | head -10 | while read -r size dir; do
printf " %-10s %s\n" "$size" "$dir"
done
echo ""
while IFS= read -r line; do
DISK_PERCENT=$(echo "$line" | awk '{gsub(/%/,""); print $5}')
DISK_MOUNT=$(echo "$line" | awk '{print $6}')
alert_check "$DISK_PERCENT" "$DISK_ALERT_THRESHOLD" "Disco ($DISK_MOUNT)"
done < <(df -h --output=pcent,target -x tmpfs -x devtmpfs -x squashfs 2>/dev/null | tail -n +2)
# ------------------------------------------
# RESUMEN FINAL
# ------------------------------------------
header "RESUMEN"
alert_check "$CPU_USAGE" "$CPU_ALERT_THRESHOLD" "CPU"
alert_check "$RAM_PERCENT" "$RAM_ALERT_THRESHOLD" "RAM"
while IFS= read -r line; do
DISK_PERCENT=$(echo "$line" | awk '{gsub(/%/,""); print $5}')
DISK_MOUNT=$(echo "$line" | awk '{print $6}')
alert_check "$DISK_PERCENT" "$DISK_ALERT_THRESHOLD" "Disco ($DISK_MOUNT)"
done < <(df -h --output=pcent,target -x tmpfs -x devtmpfs -x squashfs 2>/dev/null | tail -n +2)
echo ""
echo -e " ${BOLD}Generado:${RESET} $(date '+%Y-%m-%d %H:%M:%S')"
separator
echo ""
Copiá este script a /opt/scripts/monitor.sh, dale permisos con chmod 700 y ya lo podés correr con ./monitor.sh. Ahora vamos bloque por bloque.
Bloque 1: Configuración de umbrales
Las primeras variables son los umbrales de alerta. Cuando un recurso supera el porcentaje configurado, el script lo marca en rojo y muestra una advertencia.
CPU_ALERT_THRESHOLD=80
RAM_ALERT_THRESHOLD=80
DISK_ALERT_THRESHOLD=85
TOP_N=5
El umbral de disco es más alto (85%) porque los discos suelen tener más margen que la RAM o la CPU. El TOP_N define cuántos procesos mostrar en el ranking de consumidores. Estos son los valores que uso habitualmente, pero ajustalos según tu servidor: si tenés una app que normalmente consume 70% de RAM, bajá el umbral a 70 para que te avise antes de que sea un problema.
Bloque 2: Colores adaptativos
Los colores hacen que el reporte sea mucho más fácil de leer de un vistazo, pero si redirigís la salida a un archivo o a un pipe, los códigos de escape ANSI se convierten en basura ilegible. Por eso detectamos si hay una terminal interactiva:
if [[ -t 1 ]]; then
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
# ...
else
RED=''
YELLOW=''
GREEN=''
# ...
fi
-t 1 verifica que el file descriptor 1 (stdout) esté conectado a una terminal. Si estás corriendo el script desde cron o redirigiendo la salida, los colores se desactivan automáticamente. Es un detalle chico pero marca la diferencia cuando el output llega a un log.
Bloque 3: Funciones auxiliares
Las funciones separator, header, alert_check y progress_bar son las que le dan formato al reporte. La más interesante es progress_bar:
progress_bar() {
local percent=$1
local width=40
local filled=$(( percent * width / 100 ))
local empty=$(( width - filled ))
local color="$GREEN"
if (( percent >= 80 )); then
color="$RED"
elif (( percent >= 60 )); then
color="$YELLOW"
fi
printf " ${color}["
printf '#%.0f' $(seq 1 $filled 2>/dev/null) || true
printf '-%.0f' $(seq 1 $empty 2>/dev/null) || true
printf "] %3d%%${RESET}\n" "$percent"
}
Genera una barra como [####################--------------------] 50% que cambia de color según el porcentaje: verde hasta 59%, amarillo de 60% a 79%, rojo de 80% en adelante. El || true en los seq evita que falle cuando filled o empty es 0.
La función alert_check es la que usa el resumen final y el bloque de disco para mostrar si un recurso está en estado crítico:
alert_check() {
local value=$1
local threshold=$2
local label=$3
if (( value >= threshold )); then
echo -e " ${RED}⚠ ALERTA: $label al ${value}% (umbral: ${threshold}%)${RESET}"
else
echo -e " ${GREEN}✓ $label: ${value}% (OK)${RESET}"
fi
}
Bloque 4: Modo solo alertas para cron
Esta es la parte que permite usar el script en un cron sin llenar un log con reportes completos cada 5 minutos:
if [[ "${1:-}" == "--alert-only" ]]; then
ALERTS=0
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{printf "%d", $2 + $4}')
RAM_PERCENT=$(free | awk '/^Mem:/ {printf "%d", $3/$2 * 100}')
while IFS= read -r line; do
DISK_PERCENT=$(echo "$line" | awk '{gsub(/%/,""); print $5}')
DISK_MOUNT=$(echo "$line" | awk '{print $6}')
if (( DISK_PERCENT >= DISK_ALERT_THRESHOLD )); then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ALERTA DISCO: $DISK_MOUNT al ${DISK_PERCENT}%"
ALERTS=$((ALERTS + 1))
fi
done < <(df -h --output=pcent,target -x tmpfs -x devtmpfs -x squashfs 2>/dev/null | tail -n +2)
# ...
fi
Cuando corrés el script con --alert-only, solo imprime líneas si hay algo que supera los umbrales. Si todo está bien, no genera output. Esto es ideal para cron, porque cron te manda un mail cuando un script produce output: si no hay alertas, no hay mail.
Bloque 5: Información del sistema
El bloque más simple, pero útil para cuando tenés muchos servidores y no recordás cuál es cuál:
echo -e " ${BOLD}Hostname:${RESET} $(hostname)"
echo -e " ${BOLD}Kernel:${RESET} $(uname -r)"
echo -e " ${BOLD}SO:${RESET} $(cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'"' -f2)"
echo -e " ${BOLD}Uptime:${RESET} $(uptime -p 2>/dev/null || uptime | sed 's/.*up/up/')"
echo -e " ${BOLD}Usuarios:${RESET} $(who | wc -l) conectados"
uptime -p da el uptime en formato legible (“up 3 weeks, 2 days, 4 hours”), pero no está disponible en todos los Linux. El fallback con sed extrae la parte relevante del output clásico de uptime.
Bloque 6: Uso de CPU
Acá medimos dos cosas: el load average y el porcentaje de uso real.
LOAD_AVG=$(cat /proc/loadavg)
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{printf "%d", $2 + $4}')
El load average (que leemos de /proc/loadavg) te dice cuántos procesos están esperando para usar la CPU. Un load average de 2.0 en un servidor de 4 núcleos está bien; en uno de 1 núcleo, significa que hay cola. Por eso siempre muestro la cantidad de núcleos junto al load:
CPU_CORES=$(nproc)
echo -e " ${BOLD}Núcleos:${RESET} $CPU_CORES"
echo -e " ${BOLD}Load Average:${RESET} $LOAD_1 (1min) | $LOAD_5 (5min) | $LOAD_15 (15min)"
El uso de CPU se calcula con top en modo batch (-bn1), que corre una sola iteración y sale. Sumamos el %us (user) y %sy (system) para obtener el uso total.
Después listamos los procesos que más CPU consumen:
ps aux --sort=-%cpu | head -n $((TOP_N + 1)) | tail -n $TOP_N
El head -n $((TOP_N + 1)) incluye la cabecera de ps, y el tail -n $TOP_N la descarta. Así nos quedan solo los 5 procesos (o los que definas en TOP_N).
Bloque 7: Uso de memoria RAM
Similar al bloque de CPU, pero usando free:
RAM_INFO=$(free -h)
RAM_TOTAL=$(echo "$RAM_INFO" | awk '/^Mem:/ {print $2}')
RAM_USED=$(echo "$RAM_INFO" | awk '/^Mem:/ {print $3}')
RAM_PERCENT=$(free | awk '/^Mem:/ {printf "%d", $3/$2 * 100}')
La diferencia entre “libre” y “disponible” es importante: Linux usa la RAM libre para caché de disco, así que “libre” suele ser un número chico incluso cuando todo está bien. “Disponible” es la métrica que importa: te dice cuánta RAM puede usar realmente una aplicación nueva sin swapear.
El ranking de procesos por RAM usa el mismo patrón que el de CPU, pero ordenando por %mem:
ps aux --sort=-%mem | head -n $((TOP_N + 1)) | tail -n $TOP_N
También incluyo el swap porque cuando el swap empieza a usarse, es señal de que la RAM no alcanza y el servidor va a empezar a andar lento:
echo -e " ${BOLD}Swap:${RESET} $SWAP_USED / $SWAP_TOTAL"
Bloque 8: Uso de disco
El comando df con --output te deja elegir exactamente qué columnas mostrar, y con -x excluimos los filesystems virtuales que solo hacen ruido:
df -h --output=source,size,used,avail,pcent,target -x tmpfs -x devtmpfs -x squashfs
Después, el du de los directorios más pesados es útil para encontrar dónde se está llenando el disco:
du -h --max-depth=2 / 2>/dev/null | sort -rh | head -10
--max-depth=2 limita la recursión para que no tarde una eternidad, sort -rh ordena de mayor a menor en formato humano, y head -10 nos da los 10 más pesados. El 2>/dev/null descarta los errores de permiso que aparecen al recorrer directorios como /proc o /root.
Programación con cron
Para monitoreo automático, lo mejor es correr el modo --alert-only cada 5 minutos:
sudo crontab -e
Y agregar:
*/5 * * * * /opt/scripts/monitor.sh --alert-only >> /var/log/monitor-alerts.log 2>&1
Esto solo genera output cuando hay una alerta, así el log se mantiene chico y fácil de revisar. Si querés un reporte completo cada hora para revisar al final del día:
0 * * * * /opt/scripts/monitor.sh >> /var/log/monitor-hourly.log 2>&1
Mejoras opcionales que valen la pena
Una vez que el script base funciona, hay varias cosas que podés sumarle:
Enviar alertas por email:
# Dentro del bloque --alert-only, al final:
if (( ALERTS > 0 )); then
echo "Hay $ALERTS alertas en $(hostname)" | mail -s "Alerta servidor" tu@email.com
fi
Notificar por Slack o Discord con un webhook:
WEBHOOK_URL="https://hooks.slack.com/services/T.../B.../xxx"
if (( ALERTS > 0 )); then
curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"⚠ $ALERTS alertas en $(hostname)\"}" \
"$WEBHOOK_URL"
fi
Guardar un historial en CSV para graficar después:
CSV_FILE="/var/log/monitor-history.csv"
echo "$(date '+%Y-%m-%d %H:%M:%S'),$CPU_USAGE,$RAM_PERCENT" >> "$CSV_FILE"
Con ese CSV podés armar un gráfico simple con gnuplot o importarlo a una planilla para ver tendencias. Muy útil para anticipar cuándo se va a llenar el disco o cuándo necesitás más RAM.
Verificar servicios críticos:
for SERVICE in nginx mysql php-fpm; do
if systemctl is-active --quiet "$SERVICE"; then
echo -e " ${GREEN}✓ $SERVICE: activo${RESET}"
else
echo -e " ${RED}✗ $SERVICE: inactivo${RESET}"
fi
done
Esto es clave si tenés un servidor web: no solo monitoreás recursos, sino que también verificás que los servicios estén corriendo.
Monitorear temperatura (servidores físicos):
if command -v sensors &> /dev/null; then
echo -e " ${BOLD}Temperatura:${RESET}"
sensors | grep "Core" | while read -r line; do
echo " $line"
done
fi
Errores comunes que vale la pena evitar
- Usar
topinteractivo en un script. Siempre usátop -bn1para que corra una sola iteración en modo batch. Sin el-b,topse queda esperando input y el script nunca termina. - Confiar en el load average sin saber cuántos núcleos tiene el servidor. Un load de 4.0 puede ser normal en 8 núcleos o catastrófico en 1 núcleo. Siempre mostrá o verificá
nproc. - No excluir filesystems virtuales en
df. Sin-x tmpfs -x devtmpfs, la salida dedfse llena de filesystems que no importan y dificulta encontrar el problema real. - Correr
dudesde/sin--max-depth. En un servidor con millones de archivos, esto puede tardar minutos y consumir más recursos de los que estás intentando monitorear. - No tener en cuenta la diferencia entre RAM “libre” y “disponible”. Si mirás solo la RAM libre, vas a creer que tu servidor está al límite cuando en realidad Linux está usando esa RAM para caché, que se libera instantáneamente cuando una app la necesita.
Conclusión
Con este script ya tenés un sistema de monitoreo funcional, liviano y sin dependencias externas. Lo corrés a mano cuando entrás a revisar un servidor, lo programás con cron para alertas automáticas y le sumás las mejoras que necesites con el tiempo.
La clave del monitoreo no es tener la herramienta más sofisticada, sino tener visibilidad constante sobre lo que importa: CPU, RAM y disco. Si esos tres están bien, tu servidor probablemente esté bien. Si alguno se dispara, lo querés saber antes de que los usuarios lo noten.
En el próximo post vamos a ver automatización de certificados SSL con Let’s Encrypt y Bash, para que tus sitios siempre tengan HTTPS válido sin tener que renovarlos a mano.