Inspect Ports with ss and netstat

Learn how to inspect open ports and listening sockets on Linux using ss and netstat. Practical commands, troubleshooting tips, and what to study next—all in 320 characters.

Focus: inspect ports with ss and netstat

Sponsored

You're staring at a connection refused error, a port already in use message, or a service that's silently not listening where it should be. Without the ability to quickly inspect which ports are open, which sockets are in a listening state, and which processes own them, debugging network issues becomes a guessing game. This lesson gives you the precise tools—ss and netstat—to inspect ports with confidence, turning network mysteries into clear answers.

The problem this lesson solves

Every network service on Linux, from a web server on port 80 to a database on 5432, announces its availability by opening a socket and entering a listening state. When things go wrong—a service isn't reachable, a port is already taken, or a firewall is blocking traffic—the first step is usually to inspect what's happening at the port level.

Consider these all-too-common scenarios:

  • Port already in use: You start a new service and get Address already in use. Which process is hogging the port?
  • Service not responding: Your API is running, but clients get connection refused. Is it actually listening?
  • Security audit: You need to inventory all open ports to spot unexpected listeners.

Guessing won't cut it. You need a systematic way to inspect ports — to see exactly what's listening, on which interface, and which process owns it. That's where ss and netstat come in.

These tools read the kernel's socket table and present it in a human-friendly format. They're the network equivalent of ps for processes, giving you a live snapshot of socket activity.

Core concept / mental model

Think of your Linux system as a busy apartment building. Each network service is a resident, and each open port is a door with a specific number (like 80 for HTTP or 5432 for PostgreSQL). A socket is the doorbell and mailbox combined—it's how traffic finds its way in and out.

When a service wants to be reachable, it creates a listening socket. This is like a doorman waiting at a designated entrance. The socket has three critical attributes:

  1. Protocol: TCP (reliable, connection-oriented) or UDP (fast, no guarantees)
  2. Address: The IP address and port number it's bound to (e.g., 0.0.0.0:80 means all interfaces, port 80)
  3. State: For TCP, the state can be LISTEN, ESTABLISHED, TIME_WAIT, etc.

The kernel maintains a table of all sockets in real time. Both ss and netstat query this table, but ss is the modern, faster, and more detailed tool, while netstat is the older classic that's still ubiquitous.

Think of ss as the newer, more efficient doorman with a better walkie-talkie, and netstat as the veteran who knows every trick but takes a little longer to report.

How it works step by step

1. Start with listening sockets

The most common task is to see which services are listening for connections. This tells you what's ready to accept traffic.

For ss, the command is:

ss -tlnp
  • -t — show TCP sockets
  • -l — show only listening sockets
  • -n — show numeric addresses and ports (no DNS lookups)
  • -p — show the process that owns the socket (requires root or matching user)

For netstat, the equivalent is:

netstat -tlnp

The -p flag will show the process name and PID. Here's example output (from ss):

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=812,fd=3))
LISTEN  0       128     127.0.0.1:5432       0.0.0.0:*         users:((postgres,pid=1024,fd=7))
LISTEN  0       511     0.0.0.0:80           0.0.0.0:*         users:((nginx,pid=1109,fd=8))

Notice the Local Address column: 0.0.0.0:22 means SSH listens on all interfaces, while 127.0.0.1:5432 means PostgreSQL only accepts local connections. This distinction is crucial for security and troubleshooting.

2. Add UDP and other protocols

TCP and UDP are not the only protocols. UDP sockets don't have a listening state, but they still bind to ports. For both:

ss -ulnp   # UDP listening sockets
ss -tlnp   # TCP listening (as before)
ss -xln    # Unix domain sockets (local IPC)

You can combine flags, e.g., ss -tulnp shows both TCP and UDP listening sockets.

3. Filter by port or process

Real systems have dozens of open ports. Filtering is essential.

To find which process owns a specific port (e.g., 8080):

ss -tlnp | grep :8080
# or
ss -tlnp sport = :8080

With netstat:

netstat -tlnp | grep :8080

To filter by process name (e.g., nginx):

ss -tlnp | grep nginx

Use head to limit output: ss -tlnp | head -20.

4. Check established connections and see all sockets

Listening sockets are only half the story. To monitor active connections (e.g., to ensure your service is being hit):

ss -tnp   # all TCP sockets, including established
netstat -tnp

To see everything (all protocols, listening and connected):

ss -tanp
# or
netstat -tanp

The state column shows ESTABLISHED, TIME_WAIT, CLOSE_WAIT, etc., which are useful for diagnosing connection issues.

Hands-on walkthrough

Let's put it all together with a concrete exercise. We'll start a simple HTTP server, inspect its listening socket, then identify and debug a port conflict.

Step 1: Start a test server

Open a terminal and run a simple Python HTTP server on port 8000:

python3 -m http.server 8000

Keep it running in the background (or in a separate terminal).

Step 2: Verify the server is listening

In another terminal, run ss and grep for port 8000:

ss -tlnp | grep :8000

Expected output (your PID will differ):

LISTEN 0 128 0.0.0.0:8000  0.0.0.0:*  users:((python,pid=1234,fd=5))

Now use netstat to confirm it matches:

netstat -tlnp | grep :8000

Step 3: Identify the owning process

Using the PID from the output, confirm the process name with ps:

ps -p 1234 -o pid,comm

Step 4: Simulate a port conflict

Try to start another server on the same port. Use a different document root:

python3 -m http.server 8000 --directory /tmp

You'll see an error like OSError: [Errno 98] Address already in use. This is the classic port already in use problem. To fix it:

  1. Identify the process using the port: ss -tlnp | grep :8000
  2. Terminate it gracefully (or forcibly if needed): bash kill 1234 # Wait a moment, then if it doesn't die: kill -9 1234
  3. Restart your service on the now-free port.

Step 5: Check established connections (optional)

If you open a browser to http://localhost:8000, then run:

ss -tnp | grep :8000

You'll see a ESTABLISHED row for the active connection.

Compare options / when to choose what

While both tools inspect ports, they have differences that matter in practice:

Feature ss netstat
Speed Fast (reads /proc/net directly) Slower (parses more data)
Performance with many sockets Excellent Becomes sluggish with a few thousand connections
Output detail More columns (e.g., send-q, recv-q) Fewer columns sometimes
Availability Preinstalled on most modern distros Deprecated on some (e.g., iproute2 is preferred)
Syntax ss -tlnp sport = :8080 netstat -tlnp | grep :8080
Filtering capabilities sport, dport, address filters Only via grep/awk

When to use ss: It's the default choice on modern Linux. It's faster, more scriptable, and shows more detailed socket info. For any serious troubleshooting, prefer ss.

When to use netstat: You'll need it on older systems or minimal containers where ss isn't installed. Also, some legacy scripts still use netstat; knowing its flags is essential for maintenance.

In short: use ss going forward, but keep netstat in your back pocket for compatibility.

Troubleshooting & edge cases

Permission denied showing process info

If you run ss -tlnp as a normal user, you may see users:( without the process name. The kernel restricts process info to the owner or root. Fix: prepend sudo, e.g., sudo ss -tlnp, or use ss -tln (which omits process info).

Performance hit with many connections

On a busy server with tens of thousands of sockets, netstat can take seconds, while ss is nearly instantaneous. If netstat feels slow, switch to ss. Also, always use -n (numeric) to skip DNS lookups—they add latency.

Port appears in TIME_WAIT but not LISTEN

After a service stops, you may see sockets in TIME_WAIT. That's normal—the kernel keeps them for a short time (usually 60 seconds) to handle late packets. Your new service may fail with Address already in use if it tries to bind the same port immediately. Wait a bit, or set SO_REUSEADDR in your app.

IPv6 vs IPv4

You might see [::]:80 in output for a socket that accepts both IPv4 and IPv6. If you need IPv4-specific info, look for 0.0.0.0:80. Services bound only to IPv6 won't accept IPv4 connections unless mapped.

netstat not found

In some minimal images (e.g., Alpine), netstat may not be installed. Install it with apk add net-tools or apt install net-tools on Debian/Ubuntu. Prefer ss to avoid dependency issues.

What you learned & what's next

You've mastered the core skill of inspecting ports on Linux. You can now:

  • Explain why listening sockets matter and how the kernel tracks them
  • Use ss -tlnp to list all TCP listening sockets with process ownership
  • Use netstat -tlnp as a compatible alternative
  • Filter by port or process to pinpoint conflicts
  • Diagnose and resolve Address already in use errors

This skill is fundamental for understanding how traffic flows into your services—which ties directly into the next lesson in this track: health-checking HTTP endpoints. When you inspect ports, you're answering the question, "Is something listening?" The next lesson extends that to, "Is it actually responding correctly?"

By the end of this track, you'll be able to trace a request from DNS to a listening socket to a healthy HTTP response—the entire backbone of observability.

Practice recap

Start your Python HTTP server, then inspect its listening socket with ss -tlnp. Try to bind the same port with another service, observe the error, and resolve it by killing the first process. Then check TCP states after making a request with ss -tan | grep :8000.

Common mistakes

  • Forgetting the -p flag leads to missing process info; always include it (and sudo if needed) when you need to identify the owner.
  • Using netstat without -n causes slow DNS lookups and numeric ports hijacked by hostnames; almost never skip -n.
  • Relying on grep to filter output can miss lines if your pattern is too broad; use exact ports like :8080 or use sport = :8080 filters with ss.

Variations

  1. Use lsof -i :8080 as an alternative to identify which process owns a port, though it can be slower on active systems.
  2. For deeper inspection of socket states, use ss -tan state established to isolate active connections from the noise.
  3. In containerized environments, run ss from inside the container (using the host's /proc is not reliable) or use nsenter to inspect the host's networking namespace.

Real-world use cases

  • Debugging a web server that fails to start because port 80 is already taken by an orphaned process.
  • Auditing a hardened server to list all listening ports and confirm no unexpected services are exposed.
  • Monitoring a microservices environment to verify each container's service is listening on the expected port after deployment.

Key takeaways

  • Listening sockets are the entry points to your services; inspecting them is the first step in network troubleshooting.
  • ss is the modern, faster, and more scriptable tool; netstat is the legacy fallback for older systems.
  • Always use -tlnp to see TCP listening sockets with numeric addresses, ports, and process ownership.
  • Filter by exact port or process with ss -tlnp | grep :8080 to quickly isolate conflicts.
  • Permission issues are common; run sudo ss -tlnp to see process names for all sockets.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.