Linux Network Troubleshooting: From ping to tcpdump — A Systematic Diagnostic Workflow

A step-by-step troubleshooting guide covering the full diagnostic workflow for Linux network issues, from interface checks to deep packet inspection with tcpdump.

Prerequisites

  • Basic Linux command-line familiarity
  • Root or sudo access
Compatible with: Ubuntu 22.04+Debian 11+RHEL 9+
Terminal-style illustration for Linux Network Troubleshooting — a systematic diagnostic workflow from ping to tcpdump

Why a Systematic Approach Matters

When a production service goes down, the instinct is to SSH in and start running random commands. This wastes time and often misses the root cause. Network issues sit at a frustrating intersection: they could be your server, your cloud provider, a DNS resolver, or something in between.

A systematic, layer-by-layer approach eliminates guesswork. Start at the bottom and work up: is the interface up? Is the route correct? Is the port listening? Is DNS resolving? Answer these in order and you’ll find the problem fast.

Step 1: Is the Interface Up?

Before anything else, confirm the network interface exists and has a link.

ip link show

Look for state UP and LOWER_UP. If you see state DOWN, the interface is administratively disabled. If NO-CARRIER, there’s no physical link.

Bring it up:

sudo ip link set eth0 up

For deeper link diagnostics, use ethtool:

sudo ethtool eth0

Key fields: Speed, Duplex, Link detected. A duplex mismatch (half vs full) or a link negotiated at 100Mb/s instead of 1Gb/s explains mysterious slowness. If Link detected: no, check cables, switch ports, or virtual NIC attachments.

# Check all interfaces at once
ip -br link show

For bonded interfaces, verify all slaves are active:

cat /proc/net/bonding/bond0

Step 2: Does the Interface Have an IP?

An interface can be up but unaddressed. Check IP assignments:

ip addr show

If the interface lacks an IPv4 address, DHCP may have failed:

# Request a lease manually
sudo dhclient -v eth0

# Or with systemd-networkd
networkctl status eth0

For static IPs, verify the configuration file. On Ubuntu with netplan:

cat /etc/netplan/00-installer-config.yaml
sudo netplan apply

Multiple IPs on the same subnet? Check for conflicts:

arping -I eth0 192.168.1.100

A duplicate IP causes intermittent connectivity — packets alternate between two hosts with the same address.

Step 3: Is the Route Correct?

You can reach the local network but not the internet. Check the routing table:

ip route show

The default route should point to your gateway:

default via 192.168.1.1 dev eth0

Missing default route? Add it:

sudo ip route add default via 192.168.1.1 dev eth0

For asymmetric routing issues, trace the actual path:

traceroute 8.8.8.8
mtr -r -c 10 8.8.8.8

mtr combines ping and traceroute into a live view. Look for hops where packet loss jumps sharply — that’s your bottleneck. Loss at hop 1 means a local issue (cable, switch port). Loss deep in the path is usually the ISP.

Policy routing can silently hijack traffic. Check for additional routing tables:

ip rule list
ip route show table all

If you see rules that don’t match your expectations, a VPN client or container runtime may have inserted them.

Step 4: Is the Port Listening?

You can reach the host but the service returns “connection refused” or hangs. Check what’s listening:

ss -tlnp

The flags: -t TCP, -l listening, -n numeric (no DNS reverse lookups — faster), -p show process.

A service bound to 127.0.0.1:8080 only accepts local connections. Bound to 0.0.0.0:8080 or a specific public IP, it’s reachable remotely. This is one of the most common causes of “works locally, fails remotely.”

Fix the bind address in the application config, or use a reverse proxy.

Check socket state distribution:

ss -s

A high count of TIME-WAIT sockets means many short-lived connections are closing. This isn’t a crisis on modern kernels, but thousands of them can exhaust the local port range:

cat /proc/sys/net/ipv4/ip_local_port_range
# Default: 32768  60999

Increase if needed:

sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"

For connection tracking issues (common on NAT gateways and Docker hosts):

cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

If conntrack_count approaches conntrack_max, new connections are dropped silently. Raise the limit:

sudo sysctl -w net.netfilter.nf_conntrack_max=262144

Step 5: Is DNS Working?

DNS failures masquerade as connectivity problems. Test resolution directly:

dig +short serverhi.com
nslookup serverhi.com

If dig succeeds but your application fails, the app may use a different resolver path. Check what’s configured:

cat /etc/resolv.conf

On systemd systems, this is often a symlink to systemd-resolved’s stub resolver at 127.0.0.53:

ls -la /etc/resolv.conf
resolvectl status

systemd-resolved caches aggressively. If DNS records changed recently, flush the cache:

sudo resolvectl flush-caches

For split-DNS setups (different resolvers per domain):

resolvectl domain eth0

A query timeout could mean the resolver is unreachable, or the DNS server is rate-limiting you. Test each nameserver:

dig @1.1.1.1 serverhi.com
dig @8.8.8.8 serverhi.com

Speed matters too. A resolver 200ms away adds up across multiple lookups:

dig @1.1.1.1 serverhi.com | grep "Query time"

For persistent DNS caching across reboots, consider running a local resolver like unbound or dnsmasq in front of your upstreams.

Step 6: What’s on the Wire?

When everything above checks out but traffic still misbehaves, capture packets:

sudo tcpdump -i eth0 -nn port 443

Flags: -nn disables name resolution for both hosts and ports — faster and cleaner output. Add -v or -vv for protocol details.

Capture to a file for later analysis:

sudo tcpdump -i eth0 -w /tmp/capture.pcap -s 0 host 10.0.1.50

The -s 0 captures full packets (not just headers). Open the pcap in Wireshark or analyze with tshark:

tshark -r /tmp/capture.pcap -Y "tcp.flags.reset==1"

This filters for TCP RST packets — often a firewall or load balancer killing connections.

Common packet-level findings:

TCP retransmissions — packet loss on the path:

tshark -r /tmp/capture.pcap -Y "tcp.analysis.retransmission"

TCP Zero Window — the receiver can’t keep up, telling the sender to pause:

tshark -r /tmp/capture.pcap -Y "tcp.window_size==0"

MTU issues — packets too large for a hop, causing fragmentation failures. Look for ICMP “fragmentation needed” messages:

sudo tcpdump -i eth0 -nn icmp

Test MTU with ping, setting the DF (Don’t Fragment) bit:

ping -M do -s 1472 8.8.8.8
# 1472 bytes payload + 28 bytes headers = 1500 MTU

Reduce the payload size if it fails. The largest payload that succeeds plus 28 is your path MTU.

Step 7: Kernel-Level Network Stats

The kernel tracks extensive networking counters in /proc/net/. These reveal patterns invisible to packet captures.

TCP segment stats:

nstat -az | grep -E "TcpRetrans|TcpExtTCP"
# or
cat /proc/net/netstat | column -t

Key metrics:

  • TcpRetransSegs — total retransmitted segments. Spikes correlate with packet loss.
  • TcpExtTCPLossProbes — loss probes (proactive, not from timeout). High values suggest unreliable links.
  • TcpExtTCPTimeouts — connection-level timeouts. Indicates dead peers or firewalls dropping keepalives.

Interface-level drop counters:

ip -s link show eth0

RX/TX errors and dropped packets at the interface level point to hardware or driver problems, not application issues.

Softnet stats (per-CPU packet processing):

cat /proc/net/softnet_stat

The third column is per-CPU drops. Non-zero values mean the kernel can’t process packets fast enough — increase the netdev budget:

sudo sysctl -w net.core.netdev_budget=600

For high-throughput servers, also tune the backlog:

sudo sysctl -w net.core.netdev_max_backlog=5000

ARP Table Issues

On large Layer 2 networks, a full ARP table causes neighbor discovery failures:

ip neigh show | wc -l
cat /proc/sys/net/ipv4/neigh/default/gc_thresh3

If the neighbor table hits gc_thresh3, new entries are rejected. Bump the thresholds:

sudo sysctl -w net.ipv4.neigh.default.gc_thresh1=1024
sudo sysctl -w net.ipv4.neigh.default.gc_thresh2=2048
sudo sysctl -w net.ipv4.neigh.default.gc_thresh3=4096

Stale ARP entries with reachable state but no traffic can also cause blackholes. Flush them:

sudo ip neigh flush dev eth0

Reverse Path Filtering

A silently dropped packet with no error log is often rp_filter. The kernel drops packets where the source IP doesn’t match the interface it arrived on:

cat /proc/sys/net/ipv4/conf/all/rp_filter
# 1 = strict mode (drops asymmetric traffic)
# 2 = loose mode (accepts if any route exists)

On multi-homed servers or load balancers, strict rp_filter breaks legitimate asymmetric routing. Set to loose:

sudo sysctl -w net.ipv4.conf.all.rp_filter=2
sudo sysctl -w net.ipv4.conf.default.rp_filter=2

Real-World Scenarios

Scenario 1: Intermittent SSH Timeouts

Symptom: SSH sessions freeze for 10-30 seconds then recover.

Diagnosis:

sudo tcpdump -i eth0 -nn tcp port 22

If you see retransmissions during the freeze, check for duplex mismatch:

sudo ethtool eth0 | grep Duplex

Half-duplex on one side and full-duplex on the other causes collisions under load. Force both sides:

sudo ethtool -s eth0 speed 1000 duplex full autoneg off

Scenario 2: Application Hangs on Startup

Symptom: A Java or Node.js app takes 60+ seconds to start, then works fine.

Diagnosis: DNS timeouts. The JVM or runtime tries to resolve a hostname, the first nameserver in /etc/resolv.conf is unreachable, and the resolver waits for the full timeout (often 5 seconds) before trying the next one. Multiple lookups multiply the delay.

strace -e trace=connect -p $(pgrep java) 2>&1 | grep ":53"

Fix: Remove dead nameservers from /etc/resolv.conf, or set a shorter timeout:

# In /etc/systemd/resolved.conf
[Resolve]
DNS=1.1.1.1 8.8.8.8
FallbackDNS=
DNSOverTLS=no

Scenario 3: Port Exhaustion Under Load

Symptom: Cannot assign requested address errors under high concurrency.

Diagnosis:

ss -s | grep TCP

Tens of thousands of TIME-WAIT sockets. The local port range is exhausted:

sudo sysctl net.ipv4.ip_local_port_range

Fix:

sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

For permanent changes, add to /etc/sysctl.d/99-network.conf:

net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1

Prevention and Monitoring

Don’t wait for the outage. Monitor these proactively:

Prometheus alerts:

# Interface errors
rate(node_network_receive_errors_total[5m]) > 0

# TCP retransmissions
rate(node_netstat_Tcp_RetransSegs[5m]) > 10

# Connection tracking table near limit
node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8

Smoke-test connectivity from cron:

#!/bin/bash
# /etc/cron.d/network-smoke
*/5 * * * * root /usr/local/bin/smoke-test.sh
#!/bin/bash
# /usr/local/bin/smoke-test.sh
TARGETS=("serverhi.com" "1.1.1.1" "your-upstream-api.internal")
for t in "${TARGETS[@]}"; do
    if ! timeout 5 curl -sf "https://$t/health" > /dev/null 2>&1; then
        logger -t smoke-test "FAIL: $t unreachable"
    fi
done

Quick Reference Card

SymptomFirst CommandLikely Cause
Host unreachableip link showInterface down, no IP
Connection refusedss -tlnpService not listening, wrong bind
Connection timeoutsudo tcpdump -i eth0 -nn host XFirewall drop, routing loop
Slow responsesmtr -r -c 10 HOSTPacket loss, DNS latency
DNS failuresdig HOSTWrong resolver, systemd-resolved stale
Port exhaustionss -sToo many TIME-WAIT, low port range
Intermittent dropssudo ethtool eth0Duplex mismatch, cable

Summary

Network troubleshooting rewards discipline. Run commands in order — link, address, route, port, DNS, packets, kernel stats — and you’ll isolate the problem in minutes instead of hours. The layered approach doesn’t just find the issue faster; it prevents you from fixing the wrong thing and declaring victory prematurely.

Bookmark the commands above. When the next alert fires at 3 AM, you’ll have a checklist, not a panic.`