Why These Tools Matter
Writing network code without knowing netstat and tcpdump is like debugging C without gdb. These tools let you see exactly what your program is doing at the socket and packet level — without changing a single line of code.
netstat shows you the current state of all sockets on the system. tcpdump captures and displays the actual packets being exchanged. Together they answer: “Is my server listening?”, “Why is that connection stuck in CLOSE_WAIT?”, “Is the client actually sending data?”
netstat — Socket State Inspector
Note: On modern Linux, netstat is part of the net-tools package and is gradually being replaced by ss (from iproute2). Both commands show the same information. We cover both below.
sudo apt install net-tools (for netstat)ss is built into modern Linux — no install neededKey netstat Options
netstat in Practice
See all listening TCP servers:
netstat -tlnp
# or with ss:
ss -tlnp
# Example output:
# Proto Recv-Q Send-Q Local Address:Port Peer Address:Port State
# tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN
# tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
# tcp6 0 0 :::80 :::* LISTEN
See all established TCP connections:
netstat -tnp | grep ESTABLISHED
# Example output:
# Proto Recv-Q Send-Q Local Address Foreign Address State
# tcp 0 0 192.168.1.10:52340 93.184.216.34:80 ESTABLISHED
# tcp 0 0 192.168.1.10:8080 192.168.1.20:47891 ESTABLISHED
Look for TIME_WAIT sockets (indicates recent server restarts or high connection turnover):
netstat -an | grep TIME_WAIT | wc -l
# If this number is very high (thousands), you have a connection recycling problem
# To see them in detail:
netstat -antp | grep TIME_WAIT
Look for CLOSE_WAIT accumulation (application bug — not calling close on EOF):
netstat -antp | grep CLOSE_WAIT
# Any CLOSE_WAIT sockets lasting more than a few seconds = bug in your code
Monitor a specific port in real time:
watch -n 1 'netstat -tnp | grep :8080'
# Refreshes every second, shows all connections to/from port 8080
Count connections per state (useful for load analysis):
netstat -an | awk '/^tcp/ {print $6}' | sort | uniq -c | sort -rn
# Example output:
# 142 ESTABLISHED
# 23 TIME_WAIT
# 8 CLOSE_WAIT
# 2 LISTEN
# 1 SYN_SENT
Using ss (Modern Replacement for netstat)
ss is faster and more powerful than netstat. It reads socket info directly from the kernel via netlink, rather than parsing /proc/net/tcp.
# List all listening TCP sockets with process info:
ss -tlnp
# List all TCP connections to port 8080:
ss -tnp dst :8080
# Show sockets in TIME_WAIT:
ss -o state time-wait
# Show sockets in ESTABLISHED to a specific host:
ss -tnp dst 192.168.1.20
# Extended info (send/recv buffer sizes):
ss -tne
# Show all socket states summary:
ss -s
# Output example:
# Total: 487
# TCP: 142 (estab 98, closed 15, orphaned 3, timewait 23)
tcpdump — Packet Level Capture
While netstat shows socket states, tcpdump shows you the actual bytes flying across the network. It captures packets on a network interface and displays them in human-readable form.
tcpdump requires root (or CAP_NET_RAW capability).
# Basic syntax:
tcpdump [options] [filter-expression]
-i eth0 — capture on interface eth0 (-i any for all interfaces)-n — don’t resolve hostnames (faster, clearer)-v / -vv — more verbose (TTL, checksums, etc.)-X — show packet data in hex and ASCII-A — show packet data in ASCII only (great for HTTP)-w file.pcap — write raw packets to file for later analysis-r file.pcap — read from a saved pcap file-c N — capture only N packets then stoptcpdump Filter Expressions
Filter expressions are the most powerful feature of tcpdump. They follow BPF (Berkeley Packet Filter) syntax.
# Capture all traffic on port 8080:
tcpdump -i lo -n port 8080
# Capture TCP traffic only:
tcpdump -i eth0 tcp
# Capture traffic to/from a specific host:
tcpdump -i eth0 host 192.168.1.20
# Capture traffic to a specific host on a specific port:
tcpdump -i eth0 host 192.168.1.20 and port 80
# Capture all SYN packets (new connection attempts):
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0'
# Capture SYN+ACK packets (server responding to connects):
tcpdump -i eth0 'tcp[tcpflags] == (tcp-syn|tcp-ack)'
# Capture RST packets (abrupt connection resets):
tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0'
# Capture FIN packets (graceful closes):
tcpdump -i eth0 'tcp[tcpflags] & tcp-fin != 0'
# Exclude SSH traffic (don't clutter output when connected via SSH):
tcpdump -i eth0 not port 22
# Capture HTTP traffic and show ASCII content:
tcpdump -i eth0 -A port 80
# Capture and save to file for Wireshark analysis:
tcpdump -i eth0 port 8080 -w capture.pcap
# Read and analyse saved capture:
tcpdump -r capture.pcap -n
Practical Debugging Session: Tracing a TCP Connection
Here is a typical debugging session. We run our TCP echo server on port 9000 and use tcpdump to watch what happens.
Terminal 1 — Start tcpdump on loopback:
sudo tcpdump -i lo -n -v port 9000
Terminal 2 — Start a simple echo server:
/* echo_server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(void)
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(9000)
};
bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
listen(listenfd, 5);
printf("Echo server on port 9000\n");
int connfd = accept(listenfd, NULL, NULL);
char buf[256];
ssize_t n;
while ((n = read(connfd, buf, sizeof(buf))) > 0)
write(connfd, buf, n);
close(connfd);
close(listenfd);
return 0;
}
Terminal 3 — Connect and send data:
echo "Hello TCP" | nc 127.0.0.1 9000
tcpdump output you’ll see (annotated):
# 3-way handshake:
lo: IP 127.0.0.1.49210 > 127.0.0.1.9000: Flags [S] # SYN from client
lo: IP 127.0.0.1.9000 > 127.0.0.1.49210: Flags [S.] # SYN+ACK from server
lo: IP 127.0.0.1.49210 > 127.0.0.1.9000: Flags [.] # ACK from client
# Data transfer:
lo: IP 127.0.0.1.49210 > 127.0.0.1.9000: Flags [P.] ... "Hello TCP\n" # push
lo: IP 127.0.0.1.9000 > 127.0.0.1.49210: Flags [.] # ACK
lo: IP 127.0.0.1.9000 > 127.0.0.1.49210: Flags [P.] ... "Hello TCP\n" # echo back
# 4-way close:
lo: IP 127.0.0.1.49210 > 127.0.0.1.9000: Flags [F.] # FIN from client
lo: IP 127.0.0.1.9000 > 127.0.0.1.49210: Flags [F.] # FIN from server
lo: IP 127.0.0.1.49210 > 127.0.0.1.9000: Flags [.] # ACK
# Flags legend:
# [S] = SYN
# [S.] = SYN+ACK
# [.] = ACK (pure acknowledgment)
# [P.] = PSH+ACK (data push)
# [F.] = FIN+ACK
# [R] = RST (connection reset)
Interview Questions & Answers
netstat -tlnp | grep :PORT or ss -tlnp | grep :PORT. The -p flag shows the PID and process name. Needs root (or sudo) to see processes owned by other users.netstat -an | grep CLOSE_WAIT | wc -l. If this count grows continuously while your application runs, you have a bug where EOF is detected on a socket but close() is never called. The application is leaking sockets.tcpdump -i eth0 -w capture.pcap port 8080. This saves raw packets in pcap format. The file can then be opened in Wireshark for visual analysis, or read back with tcpdump -r capture.pcap. This is useful when you can’t run Wireshark directly on the target system (e.g., an embedded Linux board).