- Authors
- Name
- Overview
- 1. Understanding the TCP 3-Way Handshake
- 2. Common TCP Connection Issues
- 3. Debugging Tools
- 4. TCP Tuning Parameters
- 5. Connection Tracking (conntrack)
- 6. Real-World Debugging Scenarios
- 7. Automated Monitoring Scripts
- 8. Summary: TCP Debugging Checklist
Overview
When running servers or developing backend systems, you will inevitably encounter problems like "it won't connect," "I'm getting timeouts," or "the connection drops intermittently." Most of these issues occur at the TCP/IP level, and diagnosing the root cause requires a solid understanding of how TCP works and how to use the right debugging tools.
This post walks through TCP connection fundamentals to practical debugging techniques you can apply immediately in production.
1. Understanding the TCP 3-Way Handshake
1.1 Connection Establishment
TCP connections are established through a 3-way handshake.
Client Server
| |
|--- SYN (seq=x) ------->| (1) Client sends SYN
| |
|<-- SYN-ACK (seq=y, | (2) Server responds with SYN-ACK
| ack=x+1) ----------|
| |
|--- ACK (ack=y+1) ----->| (3) Client sends ACK
| |
|== Connection Established |
Problems can occur at each step, and the failure point indicates different root causes.
1.2 TCP Connection States
TCP sockets transition through various states. Understanding these states is essential for debugging.
LISTEN - Server waiting for incoming connections
SYN_SENT - Client has sent SYN, waiting for SYN-ACK
SYN_RECV - Server received SYN, sent SYN-ACK, waiting for ACK
ESTABLISHED - Connection fully established
FIN_WAIT_1 - Active close initiated (FIN sent)
FIN_WAIT_2 - Received ACK for FIN, waiting for peer's FIN
CLOSE_WAIT - Passive close (received peer's FIN)
TIME_WAIT - Waiting after close (2x MSL)
LAST_ACK - Waiting for final ACK
CLOSING - Both sides closing simultaneously
CLOSED - Connection terminated
1.3 Connection Termination (4-Way Handshake)
Client Server
| |
|--- FIN --------------->| (1) Client requests close
|<-- ACK ----------------| (2) Server acknowledges
| |
|<-- FIN ----------------| (3) Server requests close
|--- ACK --------------->| (4) Client acknowledges
| |
|-- TIME_WAIT (2MSL) ----|
2. Common TCP Connection Issues
2.1 Connection Refused
The server responds with an RST packet when no process is listening on the target port.
# Observe the symptom
$ curl -v http://192.168.1.100:8080
* Trying 192.168.1.100:8080...
* connect to 192.168.1.100 port 8080 failed: Connection refused
# Check if the port is being listened on
$ ss -tlnp | grep 8080
# (no output = no process listening)
# Check firewall rules
$ iptables -L -n | grep 8080
$ firewall-cmd --list-ports
Common causes:
- The service is not running
- The service is bound to a different port
- The service is bound only to localhost (127.0.0.1)
- A firewall is blocking the connection with REJECT
2.2 Connection Timeout
Occurs when a SYN packet is sent but no response is received.
# Symptom
$ curl --connect-timeout 5 http://10.0.0.50:3306
curl: (28) Connection timed out after 5001 milliseconds
# Check SYN retransmission statistics
$ netstat -s | grep -i retrans
12345 segments retransmitted
678 SYNs to LISTEN sockets dropped
# View kernel SYN retry count
$ cat /proc/sys/net/ipv4/tcp_syn_retries
6
# Retry intervals: 1s -> 2s -> 4s -> 8s -> 16s -> 32s (total ~63s)
Common causes:
- Routing issues (packets going to the wrong destination)
- Firewall dropping SYN packets silently (DROP instead of REJECT)
- Server overloaded and unable to respond
- Intermediate network equipment failure
2.3 SYN Flood Attacks
# Abnormally high SYN_RECV count
$ ss -s
Total: 15234
TCP: 12890 (estab 234, closed 45, orphaned 12, timewait 567)
$ ss -tn state syn-recv | wc -l
8923 # Abnormally high
# Check if SYN cookies are enabled
$ cat /proc/sys/net/ipv4/tcp_syncookies
1
# Check SYN backlog size
$ cat /proc/sys/net/ipv4/tcp_max_syn_backlog
1024
# SYN flood defense configuration
$ sysctl -w net.ipv4.tcp_syncookies=1
$ sysctl -w net.ipv4.tcp_max_syn_backlog=4096
$ sysctl -w net.ipv4.tcp_synack_retries=2
2.4 RST (Reset) Packet Analysis
RST packets are sent in various situations.
# Capture RST packets with tcpdump
$ tcpdump -i eth0 'tcp[tcpflags] & (tcp-rst) != 0' -nn
# Example output
14:23:01.123456 IP 192.168.1.10.45678 > 10.0.0.5.80: Flags [R], seq 0, ack 1234567, win 0, length 0
14:23:01.234567 IP 10.0.0.5.80 > 192.168.1.10.45678: Flags [R.], seq 0, ack 7654321, win 0, length 0
Causes of RST:
- Connection attempt to a closed port
- Application forced close (SO_LINGER configuration)
- TCP keepalive failure
- Firewall connection tracking table overflow
2.5 Excessive TIME_WAIT
# Count TIME_WAIT sockets
$ ss -s
TCP: 8934 (estab 234, closed 45, orphaned 12, timewait 8123)
# View TIME_WAIT socket details
$ ss -tn state time-wait | head -20
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 192.168.1.10:45678 10.0.0.5:80
0 0 192.168.1.10:45679 10.0.0.5:80
...
# Enable TIME_WAIT socket reuse
$ sysctl -w net.ipv4.tcp_tw_reuse=1
# Check FIN timeout (default 60 seconds, not modifiable for TIME_WAIT itself)
$ cat /proc/sys/net/ipv4/tcp_fin_timeout
60
3. Debugging Tools
3.1 ss (Socket Statistics)
ss is the modern replacement for netstat, offering faster output and more detailed information.
# List all TCP listening sockets with process info
$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5678,fd=6))
LISTEN 0 128 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=5678,fd=7))
# Filter sockets by state
$ ss -tn state established '( dport = :443 )'
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 0 192.168.1.10:45678 10.0.0.5:443
0 0 192.168.1.10:45679 10.0.0.5:443
# Include timer information (retransmission, keepalive, etc.)
$ ss -tnio
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.1.10:22 10.0.0.1:54321
cubic wscale:7,7 rto:204 rtt:1.5/0.75 ato:40 mss:1448 pmtu:1500
rcvmss:1448 advmss:1448 cwnd:10 ssthresh:20 bytes_sent:12345
bytes_acked:12345 bytes_received:6789 segs_out:100 segs_in:80
# Recv-Q/Send-Q meaning for LISTEN state:
# Recv-Q: current number of connections in the SYN backlog queue
# Send-Q: maximum size of the SYN backlog queue
3.2 netstat (Legacy but Still Useful)
# Count connections by TCP state
$ netstat -ant | awk '{print $6}' | sort | uniq -c | sort -rn
234 ESTABLISHED
123 TIME_WAIT
45 CLOSE_WAIT
12 LISTEN
5 SYN_SENT
3 FIN_WAIT2
1 SYN_RECV
# Check connections on a specific port
$ netstat -antp | grep :3306
tcp 0 0 192.168.1.10:3306 192.168.1.20:45678 ESTABLISHED 1234/mysqld
tcp 0 0 192.168.1.10:3306 192.168.1.21:34567 ESTABLISHED 1234/mysqld
# Network statistics summary
$ netstat -s | head -40
Ip:
123456 total packets received
0 forwarded
0 incoming packets discarded
123456 incoming packets delivered
654321 requests sent out
Tcp:
12345 active connection openings
6789 passive connection openings
123 failed connection attempts
45 connection resets received
234 connections established
567890 segments received
678901 segments sent out
1234 segments retransmitted
3.3 tcpdump Packet Capture
# Basic packet capture (specific host and port)
$ tcpdump -i eth0 host 10.0.0.5 and port 80 -nn
# Capture only 3-way handshake packets
$ tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' -nn
# Capture only SYN packets (SYN without ACK)
$ tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) == 0' -nn
# Save to file for Wireshark analysis
$ tcpdump -i eth0 -w /tmp/capture.pcap -c 10000 host 10.0.0.5
# Read a saved capture file
$ tcpdump -r /tmp/capture.pcap -nn
# Include packet contents (hex + ASCII)
$ tcpdump -i eth0 -X port 80 -c 5
# Example output (3-way handshake)
14:30:01.001 IP 192.168.1.10.45678 > 10.0.0.5.80: Flags [S], seq 1234567, win 64240, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0
14:30:01.002 IP 10.0.0.5.80 > 192.168.1.10.45678: Flags [S.], seq 7654321, ack 1234568, win 65160, options [mss 1460,sackOK,TS val 456 ecr 123,nop,wscale 7], length 0
14:30:01.002 IP 192.168.1.10.45678 > 10.0.0.5.80: Flags [.], ack 7654322, win 502, options [nop,nop,TS val 123 ecr 456], length 0
# Capture HTTP request/response content
$ tcpdump -i eth0 -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' -c 20
3.4 Wireshark Display Filters
Useful filters when analyzing pcap files captured by tcpdump in Wireshark.
# Basic display filters
tcp.port == 80 # Specific port
ip.addr == 192.168.1.10 # Specific IP
tcp.flags.syn == 1 && tcp.flags.ack == 0 # SYN only
tcp.flags.reset == 1 # RST packets
tcp.analysis.retransmission # Retransmitted packets
tcp.analysis.duplicate_ack # Duplicate ACKs
tcp.analysis.zero_window # Zero window
tcp.analysis.window_full # Window full
# Follow a specific TCP stream
tcp.stream eq 5
# Time-based filtering
frame.time >= "2026-03-08 14:00:00" && frame.time <= "2026-03-08 14:30:00"
# Find slow responses
tcp.time_delta > 1 # Packets delayed more than 1 second
4. TCP Tuning Parameters
4.1 Backlog Configuration
# Listen backlog (SYN queue + accept queue)
$ cat /proc/sys/net/core/somaxconn
4096
# SYN backlog (half-open connection queue)
$ cat /proc/sys/net/ipv4/tcp_max_syn_backlog
1024
# Recommended settings for high-traffic servers
$ sysctl -w net.core.somaxconn=65535
$ sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# The application must also set the backlog
# Nginx example:
# listen 80 backlog=65535;
4.2 Keepalive Configuration
# View current settings
$ cat /proc/sys/net/ipv4/tcp_keepalive_time
7200 # Default: 2 hours (idle time before first probe)
$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75 # Interval between probes (seconds)
$ cat /proc/sys/net/ipv4/tcp_keepalive_probes
9 # Maximum number of probes
# Configuration for faster dead connection detection
$ sysctl -w net.ipv4.tcp_keepalive_time=600 # 10 minutes
$ sysctl -w net.ipv4.tcp_keepalive_intvl=30 # 30 seconds
$ sysctl -w net.ipv4.tcp_keepalive_probes=5 # 5 probes
# Total detection time: 600 + (30 * 5) = 750 seconds (12.5 minutes)
4.3 Window Scaling
# Check if window scaling is enabled (default: 1)
$ cat /proc/sys/net/ipv4/tcp_window_scaling
1
# TCP buffer sizes (min, default, max)
$ cat /proc/sys/net/ipv4/tcp_rmem
4096 131072 6291456
$ cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4194304
# Optimization for high-bandwidth environments
$ sysctl -w net.ipv4.tcp_rmem="4096 262144 16777216"
$ sysctl -w net.ipv4.tcp_wmem="4096 262144 16777216"
$ sysctl -w net.core.rmem_max=16777216
$ sysctl -w net.core.wmem_max=16777216
# BDP (Bandwidth-Delay Product) calculation
# For 1 Gbps bandwidth, 10ms RTT:
# BDP = 1,000,000,000 * 0.01 / 8 = 1,250,000 bytes (~1.2 MB)
# Set tcp_rmem/tcp_wmem max values to at least the BDP
4.4 Other Important Parameters
# Ephemeral port range
$ cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999
# Expand port range
$ sysctl -w net.ipv4.ip_local_port_range="1024 65535"
# FIN timeout (time before transitioning from TIME_WAIT to CLOSED)
$ sysctl -w net.ipv4.tcp_fin_timeout=30
# Enable TCP Fast Open
$ sysctl -w net.ipv4.tcp_fastopen=3
# Maximum file descriptors (sockets are file descriptors too)
$ cat /proc/sys/fs/file-max
1048576
$ ulimit -n
65535
5. Connection Tracking (conntrack)
5.1 conntrack Basics
Linux netfilter tracks all connections. This is used for stateful filtering in iptables/nftables rules.
# View the conntrack table
$ conntrack -L | head -10
tcp 6 431999 ESTABLISHED src=192.168.1.10 dst=10.0.0.5 sport=45678 dport=80 src=10.0.0.5 dst=192.168.1.10 sport=80 dport=45678 [ASSURED] mark=0 use=1
tcp 6 116 SYN_SENT src=192.168.1.10 dst=10.0.0.50 sport=34567 dport=3306 [UNREPLIED] src=10.0.0.50 dst=192.168.1.10 sport=3306 dport=34567 mark=0 use=1
# Count conntrack entries
$ conntrack -C
45678
# Check conntrack table maximum
$ cat /proc/sys/net/nf_conntrack_max
262144
# When the conntrack table is full, new connections are DROPPED
# Check dmesg for evidence
$ dmesg | grep conntrack
[12345.678] nf_conntrack: table full, dropping packet
5.2 conntrack Optimization
# Increase conntrack maximum
$ sysctl -w net.nf_conntrack_max=1048576
# conntrack hash table size
$ cat /proc/sys/net/netfilter/nf_conntrack_buckets
65536
# Timeout settings
$ cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
432000 # 5 days (default)
# Use shorter timeouts
$ sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400 # 1 day
$ sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
$ sysctl -w net.netfilter.nf_conntrack_tcp_timeout_close_wait=60
# Delete specific connections
$ conntrack -D -s 192.168.1.100
$ conntrack -D -p tcp --dport 80
6. Real-World Debugging Scenarios
6.1 Scenario: Intermittent Web Server Timeouts
# Step 1: Check current connection state overview
$ ss -s
Total: 15234
TCP: 12890 (estab 8234, closed 45, orphaned 12, timewait 4567)
# Step 2: Check for CLOSE_WAIT accumulation (suspected socket leak)
$ ss -tn state close-wait | wc -l
2345 # Far too many! Application is not closing sockets properly
# Step 3: Identify which processes hold CLOSE_WAIT sockets
$ ss -tnp state close-wait | awk '{print $NF}' | sort | uniq -c | sort -rn
2100 users:(("java",pid=5678,fd=...))
200 users:(("python",pid=1234,fd=...))
# Step 4: Check file descriptor count for the offending process
$ ls -la /proc/5678/fd | wc -l
4567 # Confirms file descriptor leak
# Step 5: Detailed check with lsof
$ lsof -p 5678 | grep TCP | grep CLOSE_WAIT | wc -l
2100
6.2 Scenario: Microservice-to-Microservice Connection Failure
# Step 1: Verify DNS resolution
$ dig +short service-b.internal
10.0.0.50
# Step 2: Test port connectivity
$ nc -zv -w 3 10.0.0.50 8080
Connection to 10.0.0.50 8080 port [tcp/*] succeeded!
# Or on failure:
$ nc -zv -w 3 10.0.0.50 8080
nc: connect to 10.0.0.50 port 8080 (tcp) failed: Connection timed out
# Step 3: Trace the network path
$ traceroute -T -p 8080 10.0.0.50
traceroute to 10.0.0.50, 30 hops max
1 gateway (10.0.0.1) 0.5 ms 0.4 ms 0.3 ms
2 * * *
3 10.0.0.50 1.2 ms 1.1 ms 1.0 ms
# Step 4: Check for MTU issues
$ ping -M do -s 1472 10.0.0.50
PING 10.0.0.50 (10.0.0.50) 1472(1500) bytes of data.
ping: local error: message too long, mtu=1400
# Adjust MTU accordingly
$ ip link set dev eth0 mtu 1400
6.3 Scenario: High-Traffic Server Tuning
# Complete tuning configuration
cat << 'EOF' > /etc/sysctl.d/99-tcp-tuning.conf
# Backlog settings
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 65535
# TIME_WAIT optimization
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
# Keepalive
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
# Buffer sizes
net.ipv4.tcp_rmem = 4096 262144 16777216
net.ipv4.tcp_wmem = 4096 262144 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# Port range
net.ipv4.ip_local_port_range = 1024 65535
# SYN flood defense
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 2
# conntrack
net.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 86400
# TCP Fast Open
net.ipv4.tcp_fastopen = 3
# Window Scaling
net.ipv4.tcp_window_scaling = 1
EOF
# Apply the configuration
$ sysctl -p /etc/sysctl.d/99-tcp-tuning.conf
6.4 Scenario: Analyzing Slow Responses with tcpdump
# Capture traffic to a specific service for 30 seconds
$ timeout 30 tcpdump -i eth0 -w /tmp/slow.pcap host 10.0.0.5 and port 443
# Analyze capture - check for retransmissions
$ tcpdump -r /tmp/slow.pcap 'tcp[tcpflags] & (tcp-syn) != 0' -nn
# SYN retransmissions indicate initial connection problems
# Analyze capture - examine data flow timing
$ tcpdump -r /tmp/slow.pcap -ttt -nn | head -30
# -ttt shows inter-packet time deltas
00:00:00.000000 IP 192.168.1.10.45678 > 10.0.0.5.443: Flags [S], seq 123
00:00:00.001234 IP 10.0.0.5.443 > 192.168.1.10.45678: Flags [S.], seq 456, ack 124
00:00:00.000100 IP 192.168.1.10.45678 > 10.0.0.5.443: Flags [.], ack 457
00:00:00.000500 IP 192.168.1.10.45678 > 10.0.0.5.443: Flags [P.], seq 124:500
00:00:02.345678 IP 10.0.0.5.443 > 192.168.1.10.45678: Flags [P.], seq 457:1200
# The 2+ second gap above indicates server-side processing delay
# Detailed analysis with tshark (CLI Wireshark)
$ tshark -r /tmp/slow.pcap -q -z io,stat,1
# Statistics at 1-second intervals
$ tshark -r /tmp/slow.pcap -q -z conv,tcp
# Per-TCP-conversation statistics
7. Automated Monitoring Scripts
7.1 TCP State Monitor
#!/bin/bash
# tcp_monitor.sh - Monitor TCP connection states
LOG_FILE="/var/log/tcp_monitor.log"
ALERT_THRESHOLD_TW=5000 # TIME_WAIT alert threshold
ALERT_THRESHOLD_CW=100 # CLOSE_WAIT alert threshold
ALERT_THRESHOLD_SYNR=500 # SYN_RECV alert threshold
while true; do
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Count by state
ESTABLISHED=$(ss -tn state established | wc -l)
TIME_WAIT=$(ss -tn state time-wait | wc -l)
CLOSE_WAIT=$(ss -tn state close-wait | wc -l)
SYN_RECV=$(ss -tn state syn-recv | wc -l)
LISTEN=$(ss -tn state listening | wc -l)
echo "$TIMESTAMP ESTAB=$ESTABLISHED TW=$TIME_WAIT CW=$CLOSE_WAIT SYNR=$SYN_RECV LISTEN=$LISTEN" >> "$LOG_FILE"
# Alert checks
if [ "$TIME_WAIT" -gt "$ALERT_THRESHOLD_TW" ]; then
echo "$TIMESTAMP [WARN] TIME_WAIT=$TIME_WAIT exceeds threshold" >> "$LOG_FILE"
fi
if [ "$CLOSE_WAIT" -gt "$ALERT_THRESHOLD_CW" ]; then
echo "$TIMESTAMP [ALERT] CLOSE_WAIT=$CLOSE_WAIT - possible socket leak!" >> "$LOG_FILE"
fi
if [ "$SYN_RECV" -gt "$ALERT_THRESHOLD_SYNR" ]; then
echo "$TIMESTAMP [ALERT] SYN_RECV=$SYN_RECV - possible SYN flood!" >> "$LOG_FILE"
fi
sleep 10
done
7.2 conntrack Monitor
#!/bin/bash
# conntrack_monitor.sh
MAX=$(cat /proc/sys/net/nf_conntrack_max)
CURRENT=$(conntrack -C 2>/dev/null || cat /proc/sys/net/netfilter/nf_conntrack_count)
USAGE=$((CURRENT * 100 / MAX))
echo "Conntrack: $CURRENT / $MAX ($USAGE%)"
if [ "$USAGE" -gt 80 ]; then
echo "[WARN] Conntrack table usage above 80%!"
echo "Top source IPs:"
conntrack -L 2>/dev/null | awk '{print $5}' | cut -d= -f2 | sort | uniq -c | sort -rn | head -10
fi
8. Summary: TCP Debugging Checklist
When a connection problem occurs, follow this systematic approach.
1. Basic Connectivity Check
[ ] Verify network path with ping / traceroute
[ ] Test port connectivity with nc -zv or telnet
[ ] Verify DNS resolution (dig, nslookup)
2. Socket State Analysis
[ ] Check listening ports with ss -tlnp
[ ] Review overall socket statistics with ss -s
[ ] Look for abnormal states (excessive CLOSE_WAIT, SYN_RECV)
3. Packet-Level Analysis
[ ] Capture packets with tcpdump
[ ] Analyze RSTs, retransmissions, and delays
[ ] Use Wireshark for detailed inspection
4. Kernel Parameter Review
[ ] Backlog queue sizes
[ ] conntrack table utilization
[ ] File descriptor limits
5. Application Level
[ ] Connection pool configuration
[ ] Timeout settings
[ ] Application error logs
The key to TCP/IP troubleshooting is taking a layered approach. Start from the physical/network layer and work upward to the application layer, systematically narrowing down the root cause at each step.