Posted on
Artificial Intelligence

Automating Network Health Reports

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Automating Network Health Reports with Bash: Set-and-Forget Visibility for Your Linux Hosts

Ever had users say “the network feels slow” while your ad‑hoc pings look fine? Or woken up to an outage with no baseline to compare against? Automating network health reports gives you objective, time-stamped snapshots of connectivity, latency, DNS health, and interface errors—so you can spot trends before they become incidents.

This post shows how to build a lightweight, Bash-based reporting pipeline that:

  • Collects actionable network metrics

  • Stores and sends readable reports

  • Runs automatically on a schedule (systemd timers or cron)

No heavy agents. Just standard Linux tools, scripts, and a webhook (optional) for alerts.

Why automate network health reports?

  • Baselines beat hunches: A daily/hourly report establishes “normal” latency/loss, helping you detect regressions quickly.

  • Faster triage: When something breaks, you’ll already have the “last known good” path, DNS resolution times, and interface error counters.

  • Low overhead and portable: Bash + ubiquitous CLI tools means minimal dependencies, easy to maintain, and works across distros.

  • Auditable operations: Plain-text, timestamped logs help explain outages and support postmortems.

What you’ll build

  • A single Bash script that captures:

    • Ping loss/latency to multiple targets
    • DNS resolution time
    • Default route and interface status/error counters
    • Optional path-quality summary (mtr)
    • External IP and quick connectivity check
  • Scheduled runs with systemd timers or cron

  • Optional notification to a webhook (e.g., Slack)


1) Install the required tools

You’ll need iproute2 (ip and ss), mtr for path quality, dig for DNS timing, and curl for webhooks/external checks.

Apt (Debian/Ubuntu):

sudo apt update
sudo apt install -y iproute2 mtr-tiny dnsutils curl

DNF (Fedora/RHEL/CentOS Stream):

sudo dnf install -y iproute mtr bind-utils curl

Zypper (openSUSE Leap/Tumbleweed):

sudo zypper refresh
sudo zypper install -y iproute2 mtr bind-utils curl

Optional (recommended):

  • vnStat (traffic history)

    • apt: sudo apt install -y vnstat
    • dnf: sudo dnf install -y vnstat
    • zypper: sudo zypper install -y vnstat
  • iperf3 (controlled throughput tests)

    • apt: sudo apt install -y iperf3
    • dnf: sudo dnf install -y iperf3
    • zypper: sudo zypper install -y iperf3

If you plan to use cron (instead of systemd timers), install and enable it:

  • apt:

    sudo apt install -y cron
    sudo systemctl enable --now cron
    
  • dnf:

    sudo dnf install -y cronie
    sudo systemctl enable --now crond
    
  • zypper:

    sudo zypper install -y cron
    sudo systemctl enable --now cron
    

2) Create the Bash script

Save this as /usr/local/bin/network-health.sh and make it executable.

#!/usr/bin/env bash
set -euo pipefail

# Configuration (override via /etc/network-health.env if using systemd EnvironmentFile)
TARGETS=("1.1.1.1" "8.8.8.8" "github.com")
PACKETS=20
LOSS_WARN=1.0          # % packet loss warning threshold
AVG_RTT_WARN=150.0     # ms average RTT warning threshold
DNS_WARN_MS=200        # ms DNS query time warning threshold
MTR_PROBES=50
LOG_DIR="/var/log/network-health"
WEBHOOK_URL="${WEBHOOK_URL:-}"   # Optional: export WEBHOOK_URL for notifications

mkdir -p "$LOG_DIR"
timestamp="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
report="$LOG_DIR/report-$(date -u +'%Y%m%d%H%M').txt"
alerts=()

gt() { awk -v a="$1" -v b="$2" 'BEGIN{exit (a>b)?0:1}'; }

section() {
  printf "\n===== %s =====\n" "$1" | tee -a "$report"
}

log() {
  printf "%s\n" "$1" | tee -a "$report"
}

ping_summary() {
  local host="$1"
  local out loss avg
  out="$(ping -c "$PACKETS" -i 0.2 -q "$host" 2>&1 || true)"
  loss="$(printf "%s\n" "$out" | grep -oE '[0-9.]+% packet loss' | awk '{print $1}' | tr -d '%')"
  avg="$(printf "%s\n" "$out" | awk -F'/' '/rtt|round-trip/ {print $5}')"
  [ -z "${loss:-}" ] && loss=100
  [ -z "${avg:-}" ] && avg=0
  printf "Host: %s | Loss: %s%% | Avg RTT: %s ms\n" "$host" "$loss" "$avg" | tee -a "$report"

  if gt "$loss" "$LOSS_WARN"; then
    alerts+=("High loss to $host: ${loss}% > ${LOSS_WARN}%")
  fi
  if gt "$avg" "$AVG_RTT_WARN"; then
    alerts+=("High latency to $host: ${avg} ms > ${AVG_RTT_WARN} ms")
  fi
}

dns_check() {
  if command -v dig >/dev/null 2>&1; then
    local qtime
    qtime="$(dig +stats +time=2 +tries=1 example.com 2>/dev/null | awk '/Query time:/ {print $4}')"
    [ -z "${qtime:-}" ] && qtime=0
    printf "DNS: Query time to resolve example.com: %s ms\n" "$qtime" | tee -a "$report"
    if gt "$qtime" "$DNS_WARN_MS"; then
      alerts+=("Slow DNS: ${qtime} ms > ${DNS_WARN_MS} ms")
    fi
  else
    printf "DNS: dig not installed, skipping\n" | tee -a "$report"
  end
}

path_quality() {
  if command -v mtr >/dev/null 2>&1; then
    for host in "${TARGETS[@]}"; do
      printf "\n-- mtr %s (last hop summary) --\n" "$host" | tee -a "$report"
      # Report mode, wide format, show both DNS/IP, no dns resolution delay (-n is opposite), cycles = MTR_PROBES
      mtr -rwzc "$MTR_PROBES" "$host" 2>/dev/null | tail -n 5 | tee -a "$report" || \
        printf "mtr failed for %s\n" "$host" | tee -a "$report"
    done
  else
    printf "mtr: not installed, skipping path quality\n" | tee -a "$report"
  fi
}

interface_status() {
  section "Interfaces"
  ip -br addr show | tee -a "$report"
  printf "\n-- Interface error counters --\n" | tee -a "$report"
  ip -s link | tee -a "$report"
}

routing_and_extip() {
  section "Routing and External Reachability"
  ip route show default | tee -a "$report"
  printf "\n-- Route to 1.1.1.1 --\n" | tee -a "$report"
  ip route get 1.1.1.1 2>&1 | tee -a "$report"

  printf "\n-- External IP --\n" | tee -a "$report"
  curl -fsS --max-time 5 https://api.ipify.org | tee -a "$report" || echo "Unavailable" | tee -a "$report"
}

webhook_notify() {
  [ -z "${WEBHOOK_URL:-}" ] && return 0
  local summary
  if [ "${#alerts[@]}" -gt 0 ]; then
    summary="Network report ${timestamp}: $(printf '%s; ' "${alerts[@]}")"
  else
    summary="Network report ${timestamp}: OK"
  fi
  # Minimal JSON payload (Slack-compatible)
  local payload
  payload=$(printf '{"text":"%s"}' "$(printf "%s" "$summary" | sed 's/"/\\"/g')")
  curl -fsS -X POST -H 'Content-Type: application/json' -d "$payload" "$WEBHOOK_URL" >/dev/null || true
}

main() {
  : > "$report"
  printf "Network Health Report - %s (UTC)\n" "$timestamp" | tee -a "$report"

  routing_and_extip
  dns_check

  section "Ping summary"
  for t in "${TARGETS[@]}"; do
    ping_summary "$t"
  done

  section "Path quality (mtr)"
  path_quality

  interface_status

  printf "\n===== Result =====\n" | tee -a "$report"
  if [ "${#alerts[@]}" -gt 0 ]; then
    printf "WARNINGS:\n" | tee -a "$report"
    printf ' - %s\n' "${alerts[@]}" | tee -a "$report"
  else
    printf "All checks within thresholds.\n" | tee -a "$report"
  fi

  ln -sfn "$report" "$LOG_DIR/latest.txt"
  webhook_notify
}

main "$@"

Make it executable:

sudo install -m 0755 /usr/local/bin/network-health.sh /usr/local/bin/network-health.sh
sudo mkdir -p /var/log/network-health

Optional: Put configuration (including a webhook URL) in /etc/network-health.env:

sudo tee /etc/network-health.env >/dev/null <<'EOF'
WEBHOOK_URL="https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX"
TARGETS=("1.1.1.1" "8.8.8.8" "github.com")
PACKETS=20
LOSS_WARN=1.0
AVG_RTT_WARN=150.0
DNS_WARN_MS=200
MTR_PROBES=50
EOF

Note: If you run as a non-root user, most commands work fine. mtr may require elevated capability; distro packages often set the needed capability automatically.


3) Schedule it (systemd timer or cron)

Systemd timer (recommended):

Service unit /etc/systemd/system/network-health.service:

[Unit]
Description=Network Health Report

[Service]
Type=oneshot
EnvironmentFile=-/etc/network-health.env
ExecStart=/usr/local/bin/network-health.sh

Timer unit /etc/systemd/system/network-health.timer:

[Unit]
Description=Run Network Health Report hourly

[Timer]
OnCalendar=hourly
RandomizedDelaySec=300
Persistent=true

[Install]
WantedBy=timers.target

Enable and start the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now network-health.timer
systemctl list-timers | grep network-health

Classic cron (alternative):

System-wide hourly:

sudo tee /etc/cron.hourly/network-health >/dev/null <<'EOF'
#!/bin/sh
/usr/local/bin/network-health.sh
EOF
sudo chmod +x /etc/cron.hourly/network-health

Or via root’s crontab (every hour at minute 0):

sudo crontab -e
# Add:
0 * * * * /usr/local/bin/network-health.sh

4) How to read and use the reports (real-world examples)

  • Detect DNS issues:

    • If “DNS: Query time” spikes above your normal (<50 ms typical on LAN), check resolvers, upstream reachability, or DoH/DoT overhead.
  • Identify path/regional issues:

    • If loss/latency rise only to certain domains, use the mtr hop list to see where the degradation begins (peering vs local link).
  • Catch flaky links early:

    • Interface “error” or “dropped” counters increasing between reports often indicate cabling, NIC, or driver problems.
  • Validate changes:

    • After firewall or routing updates, compare the latest report to the previous hour/day for regression.

Reports are stored under /var/log/network-health/ with a latest.txt symlink for convenience. You can ship these logs with your usual log pipeline, or tail and diff them during incidents.


5) Extend and harden (optional ideas)

  • Persist traffic stats with vnStat:

    • Install vnStat (see above), enable its service, and append vnstat --oneline to the report for hourly totals.
  • Throughput tests with iperf3:

    • Schedule a small-window iperf3 test to a controlled server in another site or cloud region.
  • Per-site configs:

    • Maintain different /etc/network-health.env per role or location (e.g., branch, DC, cloud) with tailored targets and thresholds.
  • Alert routing:

    • Use different webhooks or mention channels for WARN vs OK. You can add logic to suppress “OK” messages except on change.

Quick smoke test

Run once manually to verify:

/usr/local/bin/network-health.sh
less /var/log/network-health/latest.txt

If you set a webhook, you should see a short summary message. Tune thresholds in /etc/network-health.env based on a day or two of baseline data.


Conclusion and next steps

Automated, text-based network health reports give you low-noise, high-signal insights with almost no operational burden. Start with the script above, schedule it hourly, and iterate:

  • Add or remove targets

  • Calibrate thresholds to your environment

  • Plug the reports into your chat or ticketing tools

Your next step:

  • Install the tools

  • Drop in the script

  • Turn on the timer

  • Review your first 24 hours of baselines

Small investment, big operational clarity—before the next “it feels slow” hits your inbox.