- 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 443to 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.