Back to blog

·6 min read

Linux Networking and Troubleshooting Commands: curl, ss, dig, tcpdump

Finding out step by step why a service can't be reached — ip, ping, dig, curl, ss, traceroute/mtr, nc, tcpdump and firewall commands.

linuxnetworkdevopsguide

Introduction

Behind a "the service is unreachable" complaint there can be dozens of causes: DNS resolves wrong, nothing listens on the port, a firewall blocks it, the certificate is invalid, or the app only listens on localhost. With the right commands you can tell which one in a few minutes.

This post lists the commands used to narrow the problem down layer by layer: interface → DNS → connectivity → port → application → packets.

1. Interfaces, IPs and routes

ip a                         # interfaces and IP addresses (ip addr)
ip -br a                     # brief summary
ip r                         # routing table; "default via" → gateway
ip r get 8.8.8.8             # which interface/gateway reaches this target
ip link set eth0 up
hostname -I                  # the server's IPs
curl -s ifconfig.me          # public IP as seen from outside

ifconfig, netstat and route come from the net-tools package, which most distributions no longer install by default; ip and ss replaced them.

2. Reachability: ping, traceroute, mtr

ping -c 4 example.com        # send 4 packets
ping -c 4 -W 2 10.0.1.15     # 2 s timeout
traceroute example.com       # routers along the path
tracepath example.com        # alternative that needs no root
mtr -rwc 50 example.com      # ping + traceroute, loss/latency report per hop

No ping reply doesn't mean the server is down. Many cloud providers and firewalls block ICMP; test the TCP port directly in the next step.

3. DNS: dig, nslookup, getent

dig example.com              # A record, TTL, answering server
dig +short example.com
dig example.com AAAA         # IPv6
dig example.com MX
dig example.com TXT          # SPF, verification records
dig @8.8.8.8 example.com     # ask a specific DNS server
dig +trace example.com       # resolution chain from the root
dig -x 203.0.113.10          # reverse (PTR) lookup
nslookup example.com
getent hosts example.com     # what the system actually uses (including /etc/hosts)
resolvectl status            # DNS servers on systems using systemd-resolved
cat /etc/resolv.conf

dig asks DNS directly; getent hosts returns what applications see, /etc/hosts included. If they differ, the problem is usually /etc/hosts or a local cache.

If you just changed a DNS record, the old value can stay in caches until its TTL expires; the TTL in dig output shows the time remaining.

4. HTTP: curl

curl https://api.example.com/health
curl -I https://example.com                 # headers only
curl -v https://example.com                 # DNS, TCP, TLS handshake, headers
curl -L http://example.com                  # follow redirects
curl -sS -o /dev/null -w "%{http_code}\n" https://example.com
curl -X POST https://api.example.com/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Sukru"}'
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/me
curl --max-time 5 https://example.com
curl -k https://self-signed.local           # skip certificate verification (testing only)
curl --resolve example.com:443:203.0.113.10 https://example.com   # bypass DNS, hit a specific IP

Timing output to see where a response slows down:

curl -sS -o /dev/null -w "dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n" https://example.com

--resolve is very handy for testing a new server before switching DNS, or for trying a single node behind a load balancer.

5. Ports and connections: ss, lsof

ss -tlnp                     # listening TCP ports and processes
ss -ulnp                     # listening UDP ports
ss -tnp                      # established TCP connections
ss -tn state established '( dport = :5432 )'   # connections to PostgreSQL
ss -s                        # summary statistics
sudo lsof -i :8080           # process using 8080
sudo fuser -k 8080/tcp       # kill the process using 8080
ss flagMeaning
-t / -uTCP / UDP
-lListening sockets only
-nNo name resolution (show port numbers)
-pShow the process (sudo for other users' processes)

Watch the Local Address column in ss -tlnp: 127.0.0.1:8080 is reachable only from the server itself, 0.0.0.0:8080 or *:8080 from every interface. It's the most common cause of "works locally, unreachable from outside".

6. Testing a TCP port: nc

nc -zv db.example.com 5432           # is the port open?
nc -zv 10.0.1.15 20-25               # a port range
timeout 3 bash -c '</dev/tcp/10.0.1.15/6379' && echo open || echo closed   # without nc
openssl s_client -connect example.com:443 -servername example.com </dev/null   # TLS and certificate chain

Reading the results:

ResultMeaning
succeeded / openThe port is open and a process is listening
Connection refusedThe host was reached but nothing listens on that port
TimeoutA firewall in between silently drops packets, or the host is unreachable

7. Packet capture: tcpdump

When everything else looks fine but the problem persists, look at what's actually on the wire:

sudo tcpdump -i any port 8080                     # traffic on 8080
sudo tcpdump -i eth0 host 10.0.1.15               # a specific host
sudo tcpdump -i any -nn 'tcp port 443 and host 203.0.113.10'
sudo tcpdump -i any -nn port 53                   # DNS queries
sudo tcpdump -i any -A -s 0 port 80               # print HTTP content as ASCII
sudo tcpdump -i any -w capture.pcap port 5432     # write to a file for Wireshark
sudo tcpdump -r capture.pcap

-nn skips resolving IPs and ports, so output is both faster and easier to read. If you see SYN packets leaving the client but no SYN-ACK coming back from the server, the problem is a firewall or security group in between.

8. Firewalls

# firewalld (AlmaLinux / RHEL)
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

# ufw (Ubuntu)
sudo ufw status verbose
sudo ufw allow 8080/tcp

# nftables / iptables directly
sudo nft list ruleset
sudo iptables -L -n -v

On cloud servers, check the provider's security group / cloud firewall rules in addition to the OS firewall.

9. Step-by-step checklist

https://api.example.com is unreachable:

dig +short api.example.com                      # 1. Does DNS return the right IP?
nc -zv api.example.com 443                      # 2. Is the port open from outside?
curl -v https://api.example.com/health          # 3. What do TLS and HTTP return?
# On the server:
ss -tlnp | grep -E ':443|:8080'                 # 4. Are Nginx and the app listening, on which address?
curl -s localhost:8080/health                   # 5. Does the app answer locally?
sudo firewall-cmd --list-all                    # 6. Does the firewall allow it?
sudo tail -n 50 /var/log/nginx/error.log        # 7. What does the proxy say?
journalctl -u myapp -n 100                      # 8. What does the app say?
SymptomLikely cause
Could not resolve hostDNS record missing or wrong; check with dig
Connection refusedThe service isn't running or wrong port
TimeoutFirewall / security group blocks it
Works locally, not from outsideThe app is bound to 127.0.0.1
SSL certificate problemExpired certificate, incomplete chain or domain mismatch
502 / 504The reverse proxy can't reach the app behind it, or it responds too slowly

Conclusion

Nearly every network problem can be diagnosed with the dozen or so commands in this post. The key is to keep the order: DNS first (dig), then the port (nc, ss), then the application (curl -v), and packets last (tcpdump). Each step narrows down where to look next and lets you isolate the problem in minutes instead of "trying everything".

More posts

All posts