Bash Monitoring Scripts for Servers and Containers
AI generated
Bash · Monitoring · Servers · Docker · Alerting
Bash Monitoring Scripts for Servers and Containers
Automatically Checking CPU, RAM, Disk, HTTP Health, and Docker Status

Bash-based monitoring scripts provide ready-to-use coverage for servers and containers without external dependencies. CPU/RAM/disk thresholds, HTTP health checks, Docker container status, and automatic alerts by email or webhook, all with plain shell tools and clear alerting logic.

14 min read CPU · RAM · Disk · HTTP · Docker · Alerting · Cron Linux · Bash 4.x · 5.x · Docker · Containers

1. Bash Monitoring: When It Is Enough and Where the Limits Are

Monitoring scripts in Bash are the pragmatic choice for small to medium infrastructures: no Prometheus stack, no Grafana instance, no TSDB, just a cron job that runs every five minutes and alerts immediately when something goes wrong. The barrier to entry is minimal, and every Linux server already has all the tools you need: df, free, top, curl, docker, and mail or curl for webhook alerts. Bash monitoring scripts can be built specifically for your own infrastructure without sending any data to external systems.

The limits of Bash-based monitoring show up with historical trends (no time series storage), complex alert routing rules, and large infrastructures with hundreds of servers. For those scenarios, Prometheus with Node Exporter or Datadog are the better fit. In practice, Bash monitoring scripts complement professional monitoring systems: they run directly on the box for instant local checks, while the external system provides the bigger picture. Especially for Docker containers and Magento shops running on a single server or just a handful of servers, Bash-based monitoring scripts deliver immediate detection speed with zero setup overhead.

2. Monitoring CPU and RAM Usage in Bash

The Bash monitoring script for CPU usage reads values from /proc/stat or uses top -bn1 for a snapshot. The pattern based on /proc/stat is more precise: it takes two consecutive measurements with a short pause and calculates the actual CPU usage over that measurement window. top -bn1 is easier to parse but returns the average usage since the last reboot, not the current value. For monitoring scripts that run every 5 minutes, top -bn1 is usually accurate enough and simpler to implement.

RAM monitoring in Bash monitoring scripts reads from /proc/meminfo or uses free -m. The modern pattern for RAM usage relies on the "available" value from /proc/meminfo instead of the traditional "free" value: Linux caches aggressively, and a system running at 95% RAM usage is often perfectly healthy as long as "MemAvailable" stays high enough. The monitoring script therefore calculates the actually available amount as a percentage of total memory and alerts when it drops below a defined threshold, not when "used" crosses some value.


#!/usr/bin/env bash
# resource-check.sh - CPU, RAM, and disk monitoring with threshold alerts
set -euo pipefail

# Thresholds (in percent)
CPU_THRESHOLD="${CPU_THRESHOLD:-85}"
RAM_THRESHOLD="${RAM_THRESHOLD:-90}"
DISK_THRESHOLD="${DISK_THRESHOLD:-85}"
LOAD_THRESHOLD="${LOAD_THRESHOLD:-8.0}"

HOSTNAME_SHORT=$(hostname -s)
ALERT_TRIGGERED=false
ALERT_MSG=""

alert() {
  local level="$1" msg="$2"
  ALERT_MSG+="[${level}] ${msg}\n"
  ALERT_TRIGGERED=true
  echo "[${level}] ${msg}"
}

# CPU: 1-minute load average vs CPU count
CPU_COUNT=$(nproc)
LOAD_1MIN=$(awk '{print $1}' /proc/loadavg)
LOAD_PERCENT=$(awk "BEGIN {printf \"%.0f\", ($LOAD_1MIN / $CPU_COUNT) * 100}")

if (( LOAD_PERCENT > CPU_THRESHOLD )); then
  alert "CRITICAL" "CPU load: ${LOAD_1MIN} (${LOAD_PERCENT}%) on ${CPU_COUNT} cores, threshold: ${CPU_THRESHOLD}%"
fi

# RAM: use MemAvailable (not free) for accurate available memory
MEM_TOTAL=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo)
MEM_AVAIL=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo)
MEM_USED_PCT=$(awk "BEGIN {printf \"%.0f\", (1 - $MEM_AVAIL/$MEM_TOTAL) * 100}")

if (( MEM_USED_PCT > RAM_THRESHOLD )); then
  MEM_AVAIL_MB=$(( MEM_AVAIL / 1024 ))
  alert "CRITICAL" "RAM usage: ${MEM_USED_PCT}% (only ${MEM_AVAIL_MB}MB available), threshold: ${RAM_THRESHOLD}%"
fi

# Disk: check all mounted filesystems
while IFS= read -r line; do
  usage=$(echo "$line" | awk '{gsub(/%/,"",$5); print $5}')
  mount=$(echo "$line" | awk '{print $6}')
  if (( usage > DISK_THRESHOLD )); then
    alert "WARNING" "Disk ${mount}: ${usage}% used, threshold: ${DISK_THRESHOLD}%"
  fi
done < <(df -h | grep '^/dev/' | grep -v "tmpfs\|udev")

echo "CPU: ${LOAD_PERCENT}% | RAM: ${MEM_USED_PCT}% | Alerts: ${ALERT_TRIGGERED}"

3. Disk Usage: Thresholds and Inode Checks

Disk monitoring in Bash scripts has two dimensions: block usage (how much storage space is occupied) and inode usage (how many file entries are consumed). A server can sit at 50% block usage and still go down if inodes run out, a classic problem in Magento environments, where var/cache/ or var/session/ generate millions of small files. The complete disk monitoring script checks both dimensions for every relevant filesystem.

For the inode check, use df -i instead of df -h. The monitoring pattern is identical: calculate usage as a percentage, compare it against a threshold, and fire an alert when the threshold is exceeded. In production environments, a tiered alert system is recommended: WARNING at 75%, CRITICAL at 90%. That gives you time to act before the server fails at 100%. The most common cause of inode exhaustion in Magento is the var/session/ directory filling up with old sessions; a Bash monitoring script that reports the session count daily enables proactive cleanup.

4. HTTP Health Checks with curl

HTTP health checks are one of the most valuable features of Bash monitoring scripts: they do not just test whether the server is up, they test whether the application responds correctly. The basic pattern with curl: check the HTTP status code, measure the response time, and optionally check for specific content. curl -s -o /dev/null -w "%{http_code}:%{time_total}" URL returns the status code and total time in a single call without saving the body. A Magento shop health check should test at least: the homepage (200), checkout (no redirect to 503), the admin login page (200), and one API endpoint.

The extended HTTP monitoring script checks not just the status code but also the response content: when Magento is in maintenance mode, it returns HTTP 503; when Varnish caches an error page, it returns HTTP 200, but the body contains no shop content. The pattern curl -s URL | grep -q "expected-content" returns exit code 0 when the content is present and 1 when it is missing. Combined with response time measurement, this Bash monitoring pattern catches both outages and performance degradation.


#!/usr/bin/env bash
# http-health-check.sh - HTTP endpoint monitoring with timing and content checks
set -euo pipefail

TIMEOUT="${TIMEOUT:-10}"
SLOW_THRESHOLD="${SLOW_THRESHOLD:-3.0}"  # seconds
ALERT_LOG="${ALERT_LOG:-/var/log/monitoring/http-alerts.log}"

mkdir -p "$(dirname "$ALERT_LOG")"

# Define endpoints to check
declare -A ENDPOINTS=(
  ["homepage"]="https://mironsoft.de/"
  ["shop"]="https://mironsoft.de/shop"
  ["checkout"]="https://mironsoft.de/checkout/cart"
  ["health"]="https://mironsoft.de/health_check"
)

# Optional content checks (empty = skip content check)
declare -A CONTENT_CHECK=(
  ["homepage"]="mironsoft"
  ["health"]="OK"
)

check_endpoint() {
  local name="$1" url="$2"
  local response http_code time_total

  response=$(curl -sS \
    --max-time "$TIMEOUT" \
    --write-out "\n%{http_code}:%{time_total}" \
    --output /tmp/http_check_body_$$ \
    "$url" 2>/dev/null) || {
    echo "[CRITICAL] $name: Connection failed (timeout or DNS error)" | tee -a "$ALERT_LOG"
    return 1
  }

  http_code=$(tail -1 /tmp/http_check_body_$$ 2>/dev/null | cut -d: -f1 || echo "000")
  time_total=$(tail -1 /tmp/http_check_body_$$ 2>/dev/null | cut -d: -f2 || echo "0")

  # Re-run properly: output goes to tempfile, stats to last line
  http_code=$(curl -sS --max-time "$TIMEOUT" \
    -o /tmp/http_body_$$ \
    -w "%{http_code}" "$url" 2>/dev/null || echo "000")
  time_total=$(curl -sS --max-time "$TIMEOUT" \
    -o /dev/null \
    -w "%{time_total}" "$url" 2>/dev/null || echo "0")

  # Status code check
  if [[ "$http_code" != "200" ]]; then
    echo "[CRITICAL] $name: HTTP $http_code at $url" | tee -a "$ALERT_LOG"
    rm -f /tmp/http_body_$$
    return 1
  fi

  # Response time check
  if awk "BEGIN {exit !($time_total > $SLOW_THRESHOLD)}"; then
    echo "[WARNING] $name: Slow response ${time_total}s (threshold: ${SLOW_THRESHOLD}s)" \
      | tee -a "$ALERT_LOG"
  fi

  # Content check (if defined)
  if [[ -n "${CONTENT_CHECK[$name]:-}" ]]; then
    if ! grep -qi "${CONTENT_CHECK[$name]}" /tmp/http_body_$$ 2>/dev/null; then
      echo "[CRITICAL] $name: Expected content '${CONTENT_CHECK[$name]}' not found" \
        | tee -a "$ALERT_LOG"
      rm -f /tmp/http_body_$$
      return 1
    fi
  fi

  echo "[OK] $name: HTTP $http_code in ${time_total}s"
  rm -f /tmp/http_body_$$
}

for name in "${!ENDPOINTS[@]}"; do
  check_endpoint "$name" "${ENDPOINTS[$name]}" || true
done

5. Monitoring Docker Container Status and Health

Docker monitoring in Bash checks three aspects: Is the container running at all? Is the container's health check green? And are resources (CPU/RAM) within the expected limits? For the first check, docker inspect --format '{{.State.Running}}' container-name is enough. The second check reads the health status: docker inspect --format '{{.State.Health.Status}}' container-name returns healthy, unhealthy, starting, or empty. The Bash monitoring script checks all three states and alerts when a container is not running or is marked unhealthy.

For resource monitoring of Docker containers, docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" gives a snapshot of every running container. The Bash monitoring pattern parses that output, extracts the percentage values, and compares them against defined thresholds. For Magento Docker environments (Mark Shust setup), the relevant containers are: PHP-FPM, Nginx, MySQL, Redis, and Elasticsearch. A monitoring script that checks all five containers and alerts immediately on failure or a health error can be implemented in under 50 lines.

6. Alerting: Email, Webhook, and Slack Integration

Alerting in Bash monitoring scripts typically uses three channels: email via mail or sendmail, HTTP webhooks for Slack/Teams/Discord, and direct API calls to monitoring platforms. The simplest pattern, echo "alert text" | mail -s "Subject" admin@example.com, assumes an MTA (Postfix, ssmtp, msmtp) is configured on the server. For most production servers that is already the case, so the alert lands instantly in the email and monitoring inbox.

Webhook-based alerting with curl is more flexible for Teams chats, Slack, or custom alert endpoints. The Bash monitoring pattern builds the JSON payload with a heredoc or printf and sends it via curl -X POST -H "Content-Type: application/json" -d "..." WEBHOOK_URL. Slack has a simple incoming webhook format, Teams uses the Adaptive Card format. Both can be sent directly from a Bash monitoring script without any external libraries. The pattern keeps JSON building simple: special characters in the alert text must be escaped with jq -r @json to produce valid JSON.


#!/usr/bin/env bash
# alerting.sh - Multi-channel alerting for Bash monitoring scripts
set -euo pipefail

# Configuration (set via environment or .env file)
ALERT_EMAIL="${ALERT_EMAIL:-ops@example.com}"
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
PAGERDUTY_KEY="${PAGERDUTY_KEY:-}"
HOSTNAME_SHORT=$(hostname -s)

# Alert state directory for deduplication
STATE_DIR="${STATE_DIR:-/var/lib/monitoring}"
mkdir -p "$STATE_DIR"

send_email_alert() {
  local subject="$1" body="$2"
  [[ -z "$ALERT_EMAIL" ]] && return 0

  {
    echo "Subject: [ALERT] ${HOSTNAME_SHORT}: ${subject}"
    echo "From: monitoring@${HOSTNAME_SHORT}"
    echo ""
    echo "$body"
    echo ""
    echo "--- Server: ${HOSTNAME_SHORT} | $(date '+%Y-%m-%d %H:%M:%S') ---"
  } | sendmail "$ALERT_EMAIL" 2>/dev/null || \
    echo "$body" | mail -s "[ALERT] ${subject}" "$ALERT_EMAIL" 2>/dev/null || \
    echo "[WARN] Could not send email alert" >&2
}

send_slack_alert() {
  local message="$1" level="${2:-warning}"
  [[ -z "$SLACK_WEBHOOK" ]] && return 0

  local color
  case "$level" in
    critical) color="#FF0000" ;;
    warning)  color="#FFA500" ;;
    ok)       color="#36A64F" ;;
    *)        color="#808080" ;;
  esac

  local escaped_msg
  escaped_msg=$(printf '%s' "$message" | jq -Rs .)

  curl -sS -X POST \
    -H "Content-Type: application/json" \
    -d "{
      \"attachments\": [{
        \"color\": \"${color}\",
        \"title\": \"Monitoring Alert - ${HOSTNAME_SHORT}\",
        \"text\": ${escaped_msg},
        \"footer\": \"$(date '+%Y-%m-%d %H:%M:%S')\"
      }]
    }" \
    "$SLACK_WEBHOOK" > /dev/null 2>&1 || echo "[WARN] Slack webhook failed" >&2
}

# Unified alert function
send_alert() {
  local level="$1" check_name="$2" message="$3"
  local full_msg="${level}: ${check_name}, ${message}"

  echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${full_msg}"
  send_email_alert "${check_name}: ${level}" "$full_msg"
  send_slack_alert "$full_msg" "$(echo "$level" | tr '[:upper:]' '[:lower:]')"
}

# Example usage:
# send_alert "CRITICAL" "Disk /var" "95% used on ${HOSTNAME_SHORT}"
# send_alert "WARNING" "CPU Load" "Load: 12.5 on 4 cores"

7. Alert Deduplication: No Alert Storms for Persistent Problems

The biggest problem with naive Bash monitoring scripts is the alert storm: if a problem persists (for example, disk at 95%) and monitoring runs every 5 minutes, the operations team receives 288 emails a day for the same issue. Alert deduplication is therefore an essential pattern in any production-ready Bash monitoring script. The simplest implementation uses state files: one file per check records whether the last run triggered an alert. An alert is only sent when the status has changed, either a new problem appeared or an existing one was resolved.

The state file pattern works like this: before the check, look for an alert state file for that check. If the problem occurs and no state file exists, send an alert and create the state file. If the problem is still present on the next run and the state file exists, send no alert (already sent). If the problem is resolved and the state file exists, send a recovery alert and delete the state file. This pattern reduces the alert storm to two messages per incident, one trigger alert and one recovery alert. An optional repeat logic can send a reminder every N hours if the problem remains unresolved.

8. A Simple Monitoring Dashboard in the Terminal

An interactive Bash monitoring dashboard built with watch or a custom refresh loop shows all the important metrics in a clear terminal view. The pattern watch -n 5 ./server-status.sh refreshes the output every 5 seconds and gives the operations team a real-time overview with no external tools required. The dashboard script combines every check: CPU load, RAM usage, disk usage, HTTP status of all endpoints, and Docker container status, all in one structured output with color-coded status indicators via ANSI escape codes.

For production environments without an interactive terminal, the dashboard script is useful as a daily status report: run once in the morning and emailed out. The Bash monitoring pattern produces a structured text output with every check, status symbols, and trends (comparison with yesterday's values from the state files). This morning report replaces the manual review of the most important metrics and gives a quick overview of the health of the entire infrastructure.

9. Monitoring Methods Compared

For small to medium infrastructures, there are several monitoring approaches, ranging from a minimal Bash implementation to a full monitoring stack. The right choice depends on complexity, historical data needs, and team capacity.

Method Setup Effort Historical Data Recommendation
Bash monitoring scripts Minimal (30 min) No time series Instant start, small infra
Prometheus + Node Exporter Medium (2-4h) Complete Recommended from 3+ servers
Zabbix / Nagios High (1-2 days) Complete For enterprise environments
UptimeRobot / BetterUptime Very low (15 min) HTTP only, external Complement to local monitoring
Bash + InfluxDB/TSDB Medium (2-3h) Yes (Bash writes metrics) Hybrid: simple scripts plus history

For Magento production environments running on a single server or just a few servers, the combination of Bash monitoring scripts and an external HTTP monitoring service (UptimeRobot) works well: the Bash scripts monitor local resources and application state, while the external service checks HTTP reachability from the outside and catches problems even when the server itself can no longer send an alert. This combination covers the most important failure scenarios without building a full monitoring stack.

Mironsoft

Server monitoring, infrastructure automation, and alerting

Want servers and containers monitored automatically?

We build tailored Bash monitoring scripts for your server and container stack: CPU/RAM/disk checks, HTTP health, Docker status monitoring, and alerting via email, Slack, and webhook, all without external monitoring systems.

Resource Monitoring

CPU/RAM/disk checks with configurable thresholds and alert deduplication

HTTP & Docker Health

Endpoint monitoring with content check, response time, and container status

Alerting Integration

Email, Slack webhook, and PagerDuty integration with alert deduplication

10. Summary

Bash monitoring scripts for servers and containers deliver instant, configurable monitoring without any dependency on external systems. The three core areas, resource monitoring (CPU/RAM/disk with a proper MemAvailable calculation), HTTP health checks (status code, response time, content check), and Docker container status (running, health, resources), cover the most important failure scenarios. Alert deduplication via state files prevents alert storms and reduces every incident to two messages: trigger and recovery.

A complete Bash monitoring setup consists of a resource check script, an HTTP health check script, a Docker monitor script, and a shared alerting module with email and webhook support. All four scripts run as cron jobs every 5 minutes and share the same state directory for deduplication. Add an external HTTP monitoring service for outside-in checks, and you get a complete, robust monitoring solution for Magento production environments running on one or a few servers, operational within 30 minutes.

Bash Monitoring for Servers and Containers: The Essentials at a Glance

Measure RAM Correctly

Use MemAvailable from /proc/meminfo instead of free. Linux caches aggressively, so high usage is normal. Alert only when MemAvailable drops low.

Complete HTTP Health

Status code plus response time plus content check. HTTP 200 with an error page in the body is only caught by a content check.

Alert Deduplication

State files per check: alert on status change (trigger plus recovery). No alert storm for persistent problems. Optional: reminder every N hours.

Do Not Forget Inodes

Use df -i for inode usage in addition to df -h. Magento var/cache/ and var/session/ can generate millions of files, inode exhaustion can hit before disk full.

11. FAQ: Bash Monitoring Scripts for Servers and Containers

1Bash monitoring vs. Prometheus, when to use which?
Bash is enough for 1-5 servers without trend analysis. Prometheus from 5+ servers, or when historical data is needed for capacity planning.
2MemAvailable instead of free for RAM?
Linux caches aggressively. free is almost always close to 0 without a real problem. MemAvailable shows the RAM actually available, including reclaimable cache.
3How do I prevent alert storms?
State files: alert only on status change. Trigger alert plus recovery alert equals at most 2 messages per incident.
4Docker container health in Bash?
docker inspect --format '{{.State.Health.Status}}': returns healthy/unhealthy/starting. docker inspect --format '{{.State.Running}}': is the container running.
5HTTP checks beyond the status code?
Response time with curl -w '%{time_total}'. Content check with grep -q. SSL verification happens automatically in curl. Redirect target with -w '%{redirect_url}'.
6Why monitor inodes?
At 0% free inodes, no new files can be created, even if disk space is still free. Magento generates millions of small files in var/.
7Sending Slack alerts from Bash?
curl -X POST -H 'Content-Type: application/json' -d '{"text":"..."}' WEBHOOK_URL. Escape the text with jq @sh. Keep the webhook URL in an environment variable, never hardcoded.
8How often should monitoring run?
Resources: every 5 min. HTTP checks: every 1-2 min for critical endpoints. Docker health: every 2 min. Log reports: daily in the morning.
9Checking that a cron job is really running?
Heartbeat pattern: the script writes a timestamp on every run. A second script checks how recent it is. Deadman switch: alert when the timestamp is older than 2x the cron interval.
10Monitoring SSL certificates in Bash?
openssl s_client -connect host:443 | openssl x509 -noout -enddate: extract the expiry date, convert it to days remaining, alert when under 30 days.