Debug 90% of Linux network faults with three commands

Debug Linux network issues with three tools: ss for socket state, nft for firewall rules, and tcpdump for what is really on the wire. This trio replaces the old netstat, iptables, and ifconfig workflow, and it ships by default on modern distros. Learn all three and you can fix 90% of connection faults in minutes.

Stop using deprecated tools

Many sysadmins still reach for ifconfig, netstat, and iptables out of habit. Most of them no longer ship by default. The rest are thin shims that sit on top of their modern replacements. Here is the mapping:

Old CommandModern ReplacementWhy Switch
ifconfigip addr / ip linkifconfig cannot handle network namespaces, VLANs, or bonding. Not installed by default since ~2020 on most distros.
netstatssss reads directly from kernel netlink sockets and is 10-100x faster on servers with thousands of connections.
iptablesnft (nftables )iptables is a compatibility layer on top of nftables since kernel 5.x. nftables has cleaner syntax, better performance, and native set/map support.
routeip routePart of the deprecated net-tools package.

One tool has not changed. tcpdump 4.99.6 is still the standard for packet capture. Wireshark ’s CLI tool tshark is the pick when you need to dig into a protocol.

Each tool covers one layer: ss reports what is listening and connected, nft shows what the firewall allows or drops, and tcpdump shows the traffic moving on the wire. Work through them in that order.

Check socket state with ss

Before you reach for a packet capture, check that the service is listening and that connections reach it. ss answers both in milliseconds.

To list all listening TCP sockets, run:

ss -tlnp

The flags: -t for TCP, -l for listening only, -n for numeric output (no DNS lookups), -p to show the owning process. That one command tells you if your service is up and bound to the right port.

You can filter by port to check one service. To see if nginx is listening on 443:

ss -tlnp sport = :443

Drop the -l flag to see established connections instead of listeners:

ss -tnp

This shows source, destination, and TCP state for every active connection.

To check for connection leaks, count TIME_WAIT sockets:

ss -tn state time-wait | wc -l

A big pile of TIME_WAIT sockets usually means an app opens and closes connections too fast instead of reusing them.

To diagnose slow transfers, the -i flag shows TCP internals: congestion window size (cwnd), round-trip time (rtt), and retransmit counts:

ss -ti

Add -o to see connection timers (keepalive, retransmit countdowns, TIME_WAIT expiration):

ss -tnpo

If ss -tlnp does not show your service on the expected port, the fault is in the app. Check its logs and config before you touch anything on the network side.

Inspect and trace firewall rules with nftables

If the service is listening but remote connections fail, the firewall is your next suspect.

Start by listing the full ruleset:

nft list ruleset

This prints every table, chain, and rule in a form you can read. Look for drop or reject rules that match your port and protocol.

To confirm that traffic is actually reaching a specific rule, add counters:

nft add rule inet filter input tcp dport 443 counter accept

Then inspect the chain:

nft list chain inet filter input

If the counter goes up, traffic reaches that rule. If it stays at zero, something else handles those packets, likely an earlier rule or a different chain.

For more detail, nftables tracing lets you watch every rule each packet hits, live. It usually pins down where a packet gets dropped:

# Add a trace rule to the prerouting chain
nft add rule inet filter prerouting meta nftrace set 1

# Watch the trace output
nft monitor trace

# Remove the trace rule when done (it has performance overhead)
nft delete rule inet filter prerouting handle <N>

The trace output shows the table, chain, rule, and verdict for every packet, so you can see exactly where traffic is accepted, dropped, or forwarded.

Knowing how packets flow through the kernel’s netfilter layer helps you guess which chain and hook the trace output means. The diagram below shows the full path a packet takes through nftables:

Netfilter packet flow diagram showing the path packets take through nftables chains and hooks in the Linux kernel
Image: Wikimedia Commons , GPL

A few common mistakes to check:

  • Rules added to the wrong chain (input vs. forward). Packets passing through the host, say to Docker containers, hit forward, not input.
  • A missing ct state established,related accept rule. Reply packets then get dropped, even though the first connection was allowed.
  • Docker or Podman adding their own nftables rules that clash with yours. Run nft list ruleset for the full picture, rules you never wrote included.

UFW and firewalld both write nftables rules behind the scenes. Do not trust their own view of the firewall. Read the real output of nft list ruleset, because the rules they generate sometimes differ from what they report. To see a full ruleset in action with NAT, DHCP, and traffic shaping, walk through routing and shaping traffic entirely with nftables .

Capture and analyze packets with tcpdump

When socket state and firewall rules both look fine but traffic still fails, a packet capture shows what is really on the wire.

A basic capture on port 443 looks like this:

sudo tcpdump -i eth0 -nn port 443

The -nn flag skips both DNS and service name lookups, which speeds up the output a lot.

To save a capture for later analysis in Wireshark, write to a .pcap file:

sudo tcpdump -i eth0 -w /tmp/capture.pcap port 443

You can narrow the capture to specific hosts:

sudo tcpdump -i eth0 host 192.168.1.50 and port 22

To see packet contents as ASCII (useful for plain HTTP debugging), add -A. For hex output, use -X instead:

sudo tcpdump -i eth0 -A port 80

On busy servers, limit what you capture. -s 96 grabs headers only, which is enough for most debugging and keeps files small. -c 100 stops after 100 packets:

sudo tcpdump -i eth0 -s 96 -c 100 port 443

To isolate TCP handshakes, filter for SYN and FIN flags:

sudo tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0'

This shows you whether connections open and close the way they should.

For DNS debugging, capture port 53 traffic to see every query and reply. That exposes slow lookups, NXDOMAIN failures, and queries sent to the wrong resolver:

sudo tcpdump -i eth0 port 53

Reading tcpdump output: common patterns

What You SeeWhat It Means
SYN sent, no SYN-ACK returnedFirewall or routing issue between client and server
SYN-ACK received, no ACK from clientClient-side firewall is dropping the return traffic
RST immediately after SYN-ACKThe service rejected the connection (check application logs)
Many retransmissionsPacket loss on the network path

You can also pipe tcpdump output over SSH straight into Wireshark on your desktop:

ssh server 'tcpdump -ni any -s0 -U -w - port 443' | wireshark -k -i -

The -U flag forces packet-buffered output. Packets show up in Wireshark right away instead of waiting for the buffer to fill.

Wireshark 3.6 main window showing captured network packets with protocol dissection panes
Wireshark analyzing a packet capture: the three-pane layout shows packet list, protocol details, and raw bytes
Image: Wikimedia Commons , GPL-2.0-or-later

A systematic debugging workflow

Single tools are useful, but a fixed order saves time. Follow these steps when someone reports “I cannot connect to service X on port Z from host Y”:

  1. Check the server with ss -tlnp sport = :Z. If the service is not listening, fix the app first.
  2. Check locally with curl -v localhost:Z on the server itself. If that fails, the service is set up wrong, usually a bad bind address or a broken config file.
  3. Check the firewall with nft list ruleset | grep Z. Add a rule for that port if none exists.
  4. Check from the client with curl -v server:Z, then read the failure:
    • A timeout points to a firewall or routing fault. Run tcpdump on both ends to see where packets vanish.
    • Connection refused means the service sits on the wrong interface. It may be bound to 127.0.0.1 instead of 0.0.0.0.
    • A TLS error is a cert problem. Use openssl s_client -connect server:Z to see the full handshake and cert chain.
  5. Capture packets on the server with sudo tcpdump -i eth0 -nn port Z. If SYN packets arrive but get no reply, the kernel is dropping them, usually because of the firewall or rp_filter reverse-path filtering.
  6. Check routing with ip route get <client-ip> on the server to confirm the reply path. Then run traceroute <server-ip> or mtr <server-ip> from the client to find where packets get lost.
  7. Check DNS with dig server from the client. If the hostname resolves to the wrong IP, no amount of firewall work will help.

The steps run from cheapest to priciest: one quick command first, a packet capture and route trace last. Most faults show up in steps 1-3. For a hardened nftables baseline to start from, securing a fresh server in 30 minutes covers the firewall along with SSH and kernel settings.

Network debugging workflow flowchart showing the systematic progression from checking socket state with ss through firewall inspection with nft to packet capture with tcpdump

Application-layer debugging with curl and openssl

Sometimes the TCP connection works but the protocol on top of it fails. Two tools cover this.

curl -v shows the TLS handshake, HTTP request headers, and response headers:

curl -v https://example.com

Look for cert errors, odd redirects, and HTTP status codes in the output.

To dig into TLS, openssl s_client shows the full cert chain, protocol version, cipher suite, and handshake errors:

openssl s_client -connect example.com:443 -servername example.com

The -servername flag sends the SNI extension. Servers that host several TLS certs on one IP need it.

To check just the expiry dates:

openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

An expired or wrong-name cert is one of the top causes of a broken HTTPS service. If you run many subdomains, one wildcard cert for all your subdomains cuts the list you have to watch down to one.

Advanced techniques

For containers, VPNs, and speed problems, these tricks go past the basics.

To check sockets inside Docker or Podman containers, enter the network namespace:

# Using network namespaces directly
ip netns exec <namespace> ss -tlnp

# Or use nsenter with the container's PID
nsenter -t <PID> -n ss -tlnp

MTU mismatches cause a lot of odd failures. They hit VPN tunnels and Docker overlay networks hardest, because the extra wrapper on each packet shrinks the usable MTU. Test with:

ping -M do -s 1472 <host>

If that fails with “message too long,” the path has an MTU mismatch. tcpdump will show ICMP “fragmentation needed” messages.

For NAT debugging, conntrack -L prints the kernel’s NAT table with every live DNAT and SNAT mapping. Reach for it when Docker port mappings or load balancer rules do not act the way you expect:

conntrack -L

Before you blame the network for slowness, measure real throughput with iperf3 :

# On the server
iperf3 -s

# On the client
iperf3 -c server-ip

iperf3 reports throughput, jitter, packet loss, and bandwidth per interval. Run it before and after a change to confirm the fix worked.

mtr folds ping and traceroute into one tool that probes every hop, over and over:

mtr --tcp -P 443 server-ip

The --tcp flag with -P 443 sends TCP SYN packets instead of ICMP. That helps, because many firewalls block ICMP but allow TCP on common ports.

Finally, tc -s qdisc show tells you if the kernel is shaping, queuing, or dropping packets. High drop counts on a qdisc mean traffic shaping is your speed problem. For a deeper look inside the kernel at network events and packet flows, eBPF performance monitoring traces live without any kernel code changes.

Quick reference

TaskCommand
Is the service listening?ss -tlnp sport = :PORT
What is connected?ss -tnp
Show firewall rulesnft list ruleset
Trace firewall decisionsnft monitor trace
Capture packets on a porttcpdump -i eth0 -nn port PORT
Save capture to filetcpdump -i eth0 -w file.pcap port PORT
Check TLS certificateopenssl s_client -connect host:443
Measure throughputiperf3 -c server
Trace network pathmtr --tcp -P PORT host
Show NAT translationsconntrack -L
TCP internal statsss -ti

Print this table or save it as a cheat sheet of aliases. The commands are short, so you will know them by heart after a few runs. Having them on hand during a 3 AM outage saves real time.