Posted on
Artificial Intelligence

Bash Scripts That Explain Network Errors

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

Bash Scripts That Explain Network Errors

You type a simple curl and get a wall of red: “Connection timed out.” You try SSH and see “No route to host.” Which one means DNS is broken? Which one means a firewall is dropping packets? The error text is short; the root cause is not.

This post gives you small Bash scripts that translate common network errors into plain English and point you to the next diagnostic step. They’re fast to drop into your toolbox and easy to extend.

Why this matters

  • Network errors are terse by design. “Connection refused” and “timed out” look similar but usually mean very different things.

  • The right first guess saves minutes or hours in MTTR during incidents.

  • Bash is available everywhere. These scripts run on minimal servers and containers without extra languages.

Prerequisites (install once)

These scripts use standard CLI tools. Install them with your package manager.

  • Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl iproute2 traceroute dnsutils netcat-openbsd openssl
  • Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y curl iproute traceroute bind-utils nmap-ncat openssl
  • openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl iproute2 traceroute bind-utils netcat-openbsd openssl

Notes:

  • iproute2/iproute provides ip.

  • dnsutils/bind-utils provides dig.

  • netcat-openbsd or nmap-ncat provides nc.

  • openssl is for TLS checks.

1) net-explain.sh — run any command and get a human explanation

This wrapper runs your network command, captures stderr, and explains common errors with next steps.

#!/usr/bin/env bash
# net-explain.sh — wrap a command and explain common network errors
# Usage: ./net-explain.sh curl -sS https://example.invalid

if [[ $# -lt 1 ]]; then
  echo "Usage: $0 <command> [args...]" >&2
  exit 2
fi

# Run the command, capture combined output and exit code
output="$("$@" 2>&1)"; status=$?

# Show original output
printf "%s\n" "$output"

# Only explain when non-zero status or network-ish messages appear
if [[ $status -ne 0 || "$output" =~ (refused|timed out|resolve|unreachable|denied|SSL|TLS) ]]; then
  explanation=""
  hint=""

  case "$output" in
    *"Name or service not known"*|*"Temporary failure in name resolution"*|*"Could not resolve host"*|*"getaddrinfo ENOTFOUND"*)
      explanation="DNS resolution failed."
      hint="Check resolvers (resolvectl status, /etc/resolv.conf) and try: ./explain-dns.sh <hostname>"
      ;;
    *"No route to host"*|*"EHOSTUNREACH"*|*"ENETUNREACH"*)
      explanation="Routing issue: no path to the destination."
      hint="Check default route and gateway: ./explain-route.sh <ip-or-host>"
      ;;
    *"Network is unreachable"*)
      explanation="No default route or interface is down."
      hint="Bring interface up or add a default route: ip addr; ip route"
      ;;
    *"Connection refused"*|*"ECONNREFUSED"*)
      explanation="Port is closed or service not listening; host reachable."
      hint="Verify the service and firewall. Test port explicitly: ./explain-ports.sh <host> <port>"
      ;;
    *"Connection timed out"*|*"Operation timed out"*|*"timed out"*)
      explanation="Likely firewall drop, asymmetric routing, or host down."
      hint="Trace path and test port: ./explain-route.sh <host> ; ./explain-ports.sh <host> <port>"
      ;;
    *"Permission denied (publickey)"*)
      explanation="SSH public key auth failed."
      hint="Ensure the correct private key and server-authorized key. ssh -v for details."
      ;;
    *"SSL certificate problem:"*|*"self signed certificate"*|*"verify error:num="*|*"tlsv1 alert"*|*"handshake failure"*)
      explanation="TLS handshake/verification failed."
      hint="Inspect the cert and SAN/expiry: ./explain-ssl.sh <host> [port]"
      ;;
    *)
      explanation="Unknown or less common error."
      hint="Narrow it with the focused scripts: explain-dns.sh, explain-route.sh, explain-ports.sh, explain-ssl.sh"
      ;;
  esac

  echo
  echo "Explanation: $explanation"
  echo "Next steps:  $hint"
fi

exit $status

Quick examples:

  • Name resolution error:

    • ./net-explain.sh curl -sS https://doesnotexist.tld
  • Port closed:

    • ./net-explain.sh nc -vz example.com 81
  • Timeout:

    • ./net-explain.sh curl -m 5 http://firewalled.example.com:8080

Make it executable:

chmod +x net-explain.sh

2) explain-dns.sh — explain DNS failures

This script checks your resolvers, then tries multiple query strategies to identify where DNS breaks.

#!/usr/bin/env bash
# explain-dns.sh — diagnose DNS resolution path
# Usage: ./explain-dns.sh example.com

set -euo pipefail

host="${1:-}"
if [[ -z "$host" ]]; then
  echo "Usage: $0 <hostname>" >&2
  exit 2
fi

have() { command -v "$1" >/dev/null 2>&1; }

echo "== Local resolver configuration =="
if have resolvectl; then
  resolvectl status | sed -n '1,120p' || true
elif have systemd-resolve; then
  systemd-resolve --status | sed -n '1,120p' || true
fi
echo "--- /etc/resolv.conf ---"
sed -n '1,120p' /etc/resolv.conf || true

echo
echo "== Basic A/AAAA lookups via default resolver =="
if have dig; then
  dig +short A "$host" || true
  dig +short AAAA "$host" || true
else
  echo "dig not found. Install dnsutils/bind-utils."
fi

echo
echo "== Try TCP (port 53) in case UDP is blocked =="
if have dig; then
  dig +tcp +time=3 "$host" A || true
fi

echo
echo "== Trace delegation to find where it fails =="
if have dig; then
  dig +trace "$host" A | sed -n '1,120p' || true
fi

echo
echo "== Reachability of listed nameservers (best-effort) =="
nslist=$(awk '/^nameserver/{print $2}' /etc/resolv.conf | tr '\n' ' ')
for ns in $nslist; do
  echo "Testing $ns:53 (TCP) with dig..."
  if dig +tcp @"$ns" "$host" A +time=3 +tries=1 >/dev/null 2>&1; then
    echo "OK: TCP to $ns:53 works."
  else
    echo "FAIL: Cannot reach $ns:53 over TCP (possible firewall or routing)."
  fi
done

echo
echo "== Interpretation =="
echo "- Empty +short suggests NXDOMAIN or resolver issues."
echo "- UDP blocked? TCP lookup succeeding but UDP failing is a firewall symptom."
echo "- +trace halting at a specific TLD/authoritative server points to upstream delegation problems."

3) explain-route.sh — explain “No route to host” and path issues

Find the egress interface, default route, gateway reachability, and path to the target.

#!/usr/bin/env bash
# explain-route.sh — routing and path diagnosis
# Usage: ./explain-route.sh <ip-or-host>

set -euo pipefail
dst="${1:-}"
if [[ -z "$dst" ]]; then
  echo "Usage: $0 <ip-or-host>" >&2
  exit 2
fi

have() { command -v "$1" >/dev/null 2>&1; }

echo "== IP addressing (up interfaces) =="
ip -br addr show up || true

echo
echo "== Default routes =="
ip route show default || true
ip -6 route show default || true

echo
echo "== Route to destination =="
ip route get "$dst" 2>/dev/null || echo "ip route get failed (unreachable?)"

gw=$(ip route | awk '/default/ {print $3; exit}')
if [[ -n "${gw:-}" ]]; then
  echo
  echo "== Gateway reachability =="
  if ping -c1 -W1 "$gw" >/dev/null 2>&1; then
    echo "Gateway $gw reachable via ICMP."
  else
    echo "Cannot ping gateway $gw (filtered or down)."
  fi
fi

echo
echo "== Traceroute (first 15 hops) =="
if have traceroute; then
  traceroute -n -m 15 -q 1 "$dst" || true
else
  echo "traceroute not found. Install traceroute."
fi

echo
echo "== Interpretation =="
echo "- 'Network is unreachable' -> missing default route or downed interface."
echo "- 'No route to host' after some hops -> mid-path filter or blackhole."
echo "- Gateway unreachable -> local network or VLAN issue."

4) explain-ports.sh — explain “connection refused” vs “timeout”

Test whether a TCP port is open, refused, or silently dropped.

#!/usr/bin/env bash
# explain-ports.sh — probe TCP ports and interpret results
# Usage: ./explain-ports.sh <host> <port>

set -euo pipefail

host="${1:-}"; port="${2:-}"
if [[ -z "$host" || -z "$port" ]]; then
  echo "Usage: $0 <host> <port>" >&2
  exit 2
fi

have() { command -v "$1" >/dev/null 2>&1; }

result=""
if have nc; then
  # -v verbose, -z zero-I/O, -w timeout
  if nc -vz -w3 "$host" "$port" 2>&1 | tee /tmp/nc.out.$$ | grep -qi 'succeeded'; then
    result="OPEN"
  elif grep -qi 'refused' /tmp/nc.out.$$; then
    result="REFUSED"
  else
    result="TIMEOUT_OR_FILTERED"
  fi
  rm -f /tmp/nc.out.$$
else
  # Fallback to bash /dev/tcp (no 'refused' text, but exit status signals)
  if timeout 3 bash -c ">/dev/tcp/$host/$port" 2>/dev/null; then
    result="OPEN"
  else
    # Could be refused or timeout; advise nc install
    result="UNKNOWN (install nc for detailed result)"
  fi
fi

echo "Probe result: $result"
case "$result" in
  OPEN)
    echo "Service is reachable. If app-level errors persist, check service logs."
    ;;
  REFUSED)
    echo "Host reachable; port closed or service not listening. Verify daemon is bound and firewall allows it."
    ;;
  TIMEOUT_OR_FILTERED)
    echo "Likely packet drop by firewall or routing blackhole. Trace the path and check security groups/ACLs."
    ;;
  *)
    echo "Cannot distinguish refusal vs timeout without nc."
    ;;
esac

# Helpful local check if host is this machine
if [[ "$host" == "127.0.0.1" || "$host" == "localhost" || "$host" == "$(hostname -I 2>/dev/null | awk '{print $1}')" ]]; then
  echo
  echo "== Local listeners for port $port =="
  if command -v ss >/dev/null 2>&1; then
    ss -tulpen | grep -E ":(^|.*:)$port\s" || echo "No local process is listening on $port."
  fi
fi

5) explain-ssl.sh — explain TLS handshake and certificate errors

Pin down expired certs, wrong hostnames, or chain issues quickly.

#!/usr/bin/env bash
# explain-ssl.sh — inspect TLS certificates and common failures
# Usage: ./explain-ssl.sh <host> [port=443]

set -euo pipefail
host="${1:-}"; port="${2:-443}"
if [[ -z "$host" ]]; then
  echo "Usage: $0 <host> [port]" >&2
  exit 2
fi

have() { command -v "$1" >/dev/null 2>&1; }
if ! have openssl; then
  echo "openssl not found. Install openssl." >&2
  exit 1
fi

echo "== TLS handshake and certificate =="
tmp=$(mktemp)
if ! timeout 5 openssl s_client -connect "${host}:${port}" -servername "$host" -verify_return_error -showcerts </dev/null > "$tmp" 2>&1; then
  echo "Handshake failed:"
  sed -n '1,50p' "$tmp"
  rm -f "$tmp"
  exit 1
fi

cert=$(awk '/-BEGIN CERTIFICATE-/{flag=1} flag{print} /-END CERTIFICATE-/{exit}' "$tmp")
echo "$cert" | openssl x509 -noout -subject -issuer -dates || true
echo
echo "== Subject Alternative Names (SAN) =="
echo "$cert" | openssl x509 -noout -ext subjectAltName | sed '1,2d' || true

echo
echo "== Hostname match =="
if echo "$cert" | openssl x509 -noout -ext subjectAltName | grep -qiE "DNS:.*(^|[, ])${host}([, ]|$)"; then
  echo "OK: Hostname appears in SAN."
else
  echo "Mismatch: Hostname not found in SAN. Expect browser/clients to warn."
fi

echo
echo "== Expiry check =="
not_after=$(echo "$cert" | openssl x509 -noout -enddate | cut -d= -f2)
exp_epoch=$(date -d "$not_after" +%s 2>/dev/null || gdate -d "$not_after" +%s)
now_epoch=$(date +%s)
if [[ "$exp_epoch" -le "$now_epoch" ]]; then
  echo "Expired: certificate is past Not After ($not_after)."
else
  days_left=$(( (exp_epoch - now_epoch) / 86400 ))
  echo "Valid: ~$days_left days remaining (Not After: $not_after)."
fi

rm -f "$tmp"

echo
echo "== Common fixes =="
echo "- SAN mismatch: reissue cert with correct hostnames."
echo "- Expired: renew and deploy; reload server."
echo "- Chain issues: include intermediate certs (full chain) in server config."

Real-world flow examples

  • Curl says “Could not resolve host”: run ./explain-dns.sh host.example. If TCP +trace succeed but standard UDP fails, you’re looking at a firewall blocking UDP/53.

  • SSH says “No route to host”: run ./explain-route.sh 203.0.113.10. If there’s no default route, add one; if traceroute halts at the edge, open a ticket with the upstream.

  • App cannot reach database: ./explain-ports.sh db.example 5432. If REFUSED, fix the DB listener or security group to allow the client’s IP. If TIMEOUT_OR_FILTERED, check network ACLs and firewalls.

  • Browser warns about cert: ./explain-ssl.sh site.example 443 to confirm expiry or SAN mismatch and plan the cert renewal.

Setup tips

  • Put the scripts in a tools directory in your PATH:
mkdir -p ~/bin
mv net-explain.sh explain-*.sh ~/bin/
chmod +x ~/bin/*.sh
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
  • Keep them versioned in a repo so your team can share improvements.

  • Add patterns you frequently see to net-explain.sh’s case block.

Conclusion and next step

Cryptic network errors don’t have to slow you down. With a few portable Bash scripts, you can turn “Connection timed out” into a clear, actionable plan. Install the prerequisites with apt, dnf, or zypper, drop these scripts into your PATH, and start using net-explain.sh as your default wrapper. Then iterate: teach the scripts the errors you see most and share them with your team.

Call to action:

  • Save these scripts, try them on your next failure, and note what they caught.

  • Open a repo (or gist) named bash-net-explain, commit your local tweaks, and invite PRs from your team.

  • Got a favorite error message to decode? Add it to net-explain.sh and spread the knowledge.