Every Connection Your Server Has Right Now
When a Linux server starts behaving strangely — high CPU, unexpected outbound traffic, slow response times — the first diagnostic step is not to reboot it. The first step is to find out who it is talking to. netstat and its modern replacement ss (socket statistics) answer exactly that question: what processes are listening on which ports, what remote IPs are connected, and what state every TCP session is in right now.
These two tools are available on every Linux distribution. They require no installation, no special agent, and no external service. They read directly from the kernel's network socket tables and return results in milliseconds. For a sysadmin responding to an incident — or anyone who wants to understand what a Linux machine is actually doing on the network — they are indispensable.
This guide covers both tools in detail: the syntax you need, the output you will see, how to interpret connection states, and what suspicious output looks like versus normal operation.
Why ss Replaced netstat
netstat has been part of Unix systems since the 1980s. It is part of the net-tools package, which was officially deprecated in favor of the iproute2 package around 2001. On modern Debian, Ubuntu, and RHEL/Rocky Linux systems, netstat may not even be installed by default. The replacement is ss, which ships as part of iproute2.
The functional difference matters beyond availability. netstat reads from /proc/net/tcp and /proc/net/udp, which are virtual files the kernel generates on demand. On a busy server with thousands of sockets, generating those files is expensive. ss uses the NETLINK_SOCK_DIAG kernel interface to query socket information directly through a Netlink socket, which is significantly faster and returns richer data including TCP internal state details like congestion window size and retransmit counters.
In practice: use ss on any system where it is available. Use netstat on older systems or when you specifically need its output format for scripting legacy tooling.
The Core Commands You Need
Listing All Listening Ports
The most common starting point is finding every port your system is listening on — meaning every port that has a service waiting for inbound connections:
ss -tulpn
Breaking down the flags: -t shows TCP sockets, -u shows UDP sockets, -l filters to listening sockets only, -p shows the process name and PID, and -n shows raw port numbers instead of resolving service names (faster, unambiguous). The equivalent netstat command is netstat -tulpn.
The output columns are: Netid (tcp/udp), State (LISTEN), Recv-Q, Send-Q, Local Address:Port, Peer Address:Port, and Process. A *:22 in the Local Address column means the SSH daemon is listening on port 22 on all interfaces. A 127.0.0.1:3306 means MySQL is listening only on localhost — it is not accessible from outside the machine.
Listing All Established Connections
To see every active connection — remote IPs that are currently talking to your server:
ss -tnp state established
Or with netstat: netstat -tnp | grep ESTABLISHED
This shows you remote IP addresses and ports. Each row is a live session. A web server might show dozens of connections to port 443 from various client IPs — that is normal. An SSH server should show very few connections, and you should recognize every source IP.
Showing Everything at Once
ss -antp shows all TCP sockets regardless of state, including listening, established, time-wait, and close-wait. The -a flag is the key addition. This is useful for getting a complete picture of everything happening on TCP before filtering down.
Understanding TCP Connection States
The State column in the output is not noise — it tells you exactly where each connection is in its lifecycle. Knowing what is normal versus suspicious requires understanding these states.
| State | Meaning | Normal to see? |
|---|---|---|
| LISTEN | Port is open and waiting for incoming connections | Yes — expected for all services |
| ESTABLISHED | Active two-way connection in progress | Yes — ongoing sessions |
| TIME_WAIT | Connection closed, waiting for late packets | Yes — persists for ~60–120 seconds |
| CLOSE_WAIT | Remote end closed, local end has not yet | Small numbers OK; many indicate app bug |
| SYN_SENT | Local machine initiated a connection, waiting for response | Briefly — suspicious if persistent |
| SYN_RECV | Connection request received, completing handshake | Briefly — many may indicate SYN flood |
| FIN_WAIT1 | Local end sent FIN, waiting for ACK | Briefly — part of normal teardown |
| FIN_WAIT2 | Local FIN acknowledged, waiting for remote FIN | Briefly — should not persist |
| LAST_ACK | Both sides closing, waiting for final ACK | Briefly — part of normal teardown |
The states to pay attention to in security contexts: ESTABLISHED connections to unknown remote IPs, large numbers of CLOSE_WAIT sockets (often indicates a process not closing connections properly, which can be exploited), and persistent SYN_RECV counts (SYN flood indicator).
Filtering Output to Find What Matters
On a busy server, raw ss or netstat output can be hundreds of lines. Filtering is essential.
Filter by Port
ss -tnp 'sport = :443' — shows all connections on local port 443 (HTTPS traffic).
ss -tnp 'dport = :443' — shows all connections where your server is connecting outbound to port 443 on remote hosts. Unexpected outbound HTTPS connections are a common indicator of malware phoning home.
Filter by State
ss -tnp state established state close-wait — show only established and close-wait sockets. Useful for quickly finding sessions that should have terminated but have not.
Filter by Remote IP
ss -tnp dst 203.0.113.50 — shows all connections to or from a specific IP. Useful when you have identified a suspicious IP and want to see which local process is communicating with it.
Count TIME_WAIT Sockets
ss -s prints a summary showing total counts by state. This is the fastest way to check if TIME_WAIT accumulation is becoming a resource issue on a high-traffic server.
Comparing ss and netstat Output
| Feature | ss | netstat |
|---|---|---|
| Package | iproute2 (modern default) | net-tools (deprecated) |
| Speed on many sockets | Fast (Netlink kernel interface) | Slower (/proc file generation) |
| Shows process names | Yes (-p flag) | Yes (-p flag) |
| TCP internal state (cwnd, rtt) | Yes (-i flag) | No |
| Filter expressions | Rich built-in filter syntax | Requires grep/awk piping |
| Output format familiarity | Slightly different columns | Classic, widely documented |
| Available by default | Yes on modern distros | Must install net-tools |
Real-World Security Investigation Workflow
Here is a concrete workflow for investigating a server you suspect has been compromised:
- List all listening ports:
ss -tulpn— document every port, the process name, and PID. Compare against your known-good baseline. Any new listener is immediately suspicious. - List all established connections:
ss -tnp state established— look at every remote IP. Research unknown IPs with a WHOIS lookup or threat intelligence feed. An established connection to a cloud VPS in an unexpected country is a red flag. - Identify the process behind a suspicious port:
ss -tnp 'sport = :4444'reveals the process listening on port 4444. Note the PID, then runls -la /proc/<PID>/exeto find the actual binary. Malware often masquerades as a system process but the binary path will reveal its true location. - Check for outbound connections to unusual ports:
ss -tnp state established | awk '{print $5}' | cut -d: -f2 | sort | uniq -c | sort -rn— this counts connections by remote port, revealing if the server is making many outbound connections to a non-standard port like 6667 (IRC, often used by botnets). - Cross-reference with lsof:
lsof -i -n -Pprovides complementary data — it lists file descriptors including network sockets, helping correlate a socket seen insswith the exact file a process has open.
Common Misconceptions
Misconception 1: "No listening ports means the server is secure"
A server with no listening ports cannot accept inbound connections — that is true. But established connections and outbound connections are not covered by that logic. Malware that phones home by initiating an outbound connection will not appear as a listening port. Always check established connections, not just listeners.
Misconception 2: "If I do not recognize a process name, it is malware"
Legitimate system services often have non-obvious names. master is the Postfix mail daemon. beam.smp is the Erlang runtime (used by RabbitMQ, CouchDB). Before concluding a process is malicious, look up the binary path and cross-reference with your installed packages using dpkg -S /path/to/binary or rpm -qf /path/to/binary.
Misconception 3: "TIME_WAIT sockets are a problem"
TIME_WAIT is a normal TCP state. Every short-lived connection (like an HTTP/1.1 request) ends in TIME_WAIT for approximately 60–120 seconds. On a high-traffic web server, seeing thousands of TIME_WAIT sockets is completely normal. They are not consuming active resources — they are just held briefly to catch delayed packets from the closed connection.
Misconception 4: "netstat -a shows all sockets"
The -a flag shows all sockets that are in a listening or connected state, but it does not show UDP sockets without -u, and it does not show Unix domain sockets without -x. For a truly complete picture, use ss -a --all or combine flags explicitly. Missing Unix domain sockets is particularly relevant for detecting malware that communicates via local socket files instead of TCP.
Pro Tips for Network Socket Analysis
- Run ss with sudo to see process information. Without root or sudo, the
-pflag only shows process details for sockets owned by your own user. On multi-user servers, you need root to see which process owns every socket. - Establish a baseline. On a freshly configured server, run
ss -tulpnand save the output. Any deviation from this baseline is a change that warrants investigation. Automated tools likeaideor custom cron-based scripts can alert you when the listening port set changes. - Use watch for real-time monitoring.
watch -n 2 'ss -tnp state established'refreshes the established connection list every 2 seconds. Useful during an active incident to see connections appearing and disappearing in real time. - Combine ss with tcpdump for verification. If
ssshows a suspicious established connection to a remote IP, runtcpdump -nn host <remote-ip>in parallel to see the actual packet content. This confirms whether real data is being exchanged and in which direction. - Check for hidden sockets with /proc. Some rootkits hide sockets from
ssandnetstatby hooking kernel interfaces. If you suspect a rootkit, comparess -tnpoutput against the raw contents of/proc/net/tcpand/proc/net/tcp6. Discrepancies indicate kernel-level manipulation. - Use ip route to cross-check. If you see connections to an unexpected subnet, run
ip routeto confirm whether there is a route to that network. A connection to a private RFC 1918 address on a public-facing server may indicate VPN or tunnel activity you were not aware of.
Scripting, JSON, and noise you should expect
ss -Htan sport = :443 filters without column headers for log pipelines; combine with awk thresholds to alert on connection fan-out. ip -json neigh show complements ss when correlating next-hop MACs. Large TIME-WAIT counts after HTTP/2 bursts are usually benign—differentiate from SYN floods using rate metrics on SYN-RECV.
Knowing which IPs your server is talking to is not optional operational knowledge — it is the baseline for any security posture. ss gets you there in under a second. Make it part of your standard incident response runbook. Check your public IP address and connection details instantly.