Network debugging toolkit

ping and mtr for reachability, dig for names, curl -v and openssl s_client for HTTP and TLS, ss for sockets, and tcpdump for when nothing else explains it.

Layer by layer

# 1. is the name resolving?
dig +short api.example.com

# 2. is the host reachable, and is there loss?
ping -c 4 api.example.com
mtr -rwzc 20 api.example.com

# 3. is the port open and accepting?
nc -vz api.example.com 443
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://api.example.com/health

# 4. is the protocol conversation correct?
curl -v https://api.example.com/health 2>&1 | head -40
LayerToolQuestion it answers
Name resolutiondig, getent hostsDoes the name resolve, to the right address?
Reachabilityping, mtrIs there a path, and is it losing packets?
Port statenc -vz, ss -tlnpIs something listening and accepting?
HTTP behaviourcurl -vStatus, headers, redirects, timing
TLSopenssl s_clientChain, protocol, ALPN, expiry
Socketsss, lsof -iWhich process holds which connection
PacketstcpdumpWhat actually went over the wire

Reading curl timings

curl -sS -o /dev/null -w '
dns      %{time_namelookup}s
connect  %{time_connect}s
tls      %{time_appconnect}s
ttfb     %{time_starttransfer}s
total    %{time_total}s
http     %{http_code}
size     %{size_download} bytes
' https://api.example.com/health
  • A large time_namelookup means DNS is slow, not the server.
  • A large gap between time_connect and time_appconnect points at the TLS handshake.
  • A large gap from time_appconnect to time_starttransfer is server processing — the request is on the wire, waiting for the app.
  • A high total with a low TTFB means the transfer is slow, so look at payload size and bandwidth.
  • Repeat with --resolve to bypass DNS and compare, which isolates the resolver quickly.
# pin the address to test one backend behind a load balancer
curl -v --resolve api.example.com:443:203.0.113.10 https://api.example.com/health

# show only the handshake and headers
curl -sS -v -o /dev/null https://api.example.com/health 2>&1 | grep -E '^[<>*]'

When nothing above explains it

# capture on a specific interface and port, without resolving names
tcpdump -i eth0 -nn -s 0 port 443 -c 50 -w /tmp/cap.pcap

# show TCP flags, which reveals retransmits and resets
tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-rst) != 0'

# a connection refused arrives as a reset, not silence
# silence usually means a firewall dropped the packet
ObservationLikely meaning
SYN sent, no replyA firewall is dropping silently
SYN sent, RST returnedThe port is closed; nothing is listening
SYN, SYN+ACK, then RST from clientThe client rejected the handshake; check for an L7 proxy
Repeated retransmissions of the same segmentPacket loss or an MTU problem
Connection resets after a period of idlenessAn idle timeout in a NAT or firewall
TLS alert immediately after the handshakeCertificate or protocol mismatch
⚠️
Capture on the smallest scope you can. A packet capture contains credentials, tokens and personal data, and a full-interface capture on a busy host fills a disk in minutes. Filter by port and host, cap the packet count, and delete the capture when you are done.

FAQ

Why does curl work but my application cannot connect?
Usually a proxy setting. curl may be honouring HTTPS_PROXY while the application is not, or the other way round. Compare environments before changing any code.
Why is ping blocked but the service reachable?
ICMP is frequently filtered while TCP is permitted. Reachability should be tested with a TCP connect, not with ping.

TCP in depth: handshake, windows and congestion control Proxies, load balancers and CDNs

Last refreshed 2026-09-18.