Ports and Connections
A network service becomes much easier to reason about once you can connect three things together: a process, a socket, and a port. Linux may have dozens of processes running, but only some of them are listening for network connections, and even then they may be listening only on specific addresses or interfaces.
This lesson builds that model and then uses two practical tools, ss and nc, to inspect and create network connections. We’ll also build a tiny Python listener so you can see exactly how a process becomes reachable through an IP address and port.
What Is A Port?
An IP address identifies a host at the IP layer.
A port identifies a network endpoint associated with a particular transport protocol on that host.
For example:
192.168.1.20:22means:
IP address → 192.168.1.20
Port → 22Port 22 is conventionally used by SSH, while HTTP commonly uses 80 and HTTPS commonly uses 443.
A useful simplified model is:
IP address
↓
Which machine?
Port
↓
Which network service on that machine?The combination is what allows multiple services to use the same IP address.
For example:
192.168.1.20:22 → SSH
192.168.1.20:80 → HTTP
192.168.1.20:443 → HTTPSAll three can coexist because the transport layer distinguishes their ports.
TCP And UDP Ports
Ports exist with transport protocols such as TCP and UDP.
That means these are conceptually different endpoints:
TCP 192.168.1.20:53
UDP 192.168.1.20:53They can coexist because TCP and UDP maintain separate transport namespaces.
TCP is connection-oriented. A TCP connection is established between two endpoints and maintains state while the connection exists.
UDP is connectionless. Applications send individual datagrams without establishing a TCP-style connection first.
For this lesson, most of the practical demonstrations will use TCP because it makes the relationship between listening services and established connections easier to see.
What Is A Socket?
A socket is the operating system interface through which a process communicates over a network.
A simplified picture is:
Process
│
▼
Socket
│
▼
TCP / UDP
│
▼
IP
│
▼
Network InterfaceWhen a server application listens on:
0.0.0.0:8080it has created a socket that is prepared to accept connections on TCP port 8080 across the machine’s IPv4 interfaces.
When a client connects to it, the kernel tracks the resulting connection as another socket.
This is why looking at sockets can tell you a great deal about what the machine is doing on the network.
Listening And Established Connections
There are two states you will encounter constantly when inspecting TCP sockets.
A listening socket is waiting for incoming connections:
LISTENAn established connection is an active TCP connection between a local endpoint and a remote endpoint:
ESTABConceptually:
Server
192.168.1.20:8080
│
│ LISTEN
▼
waiting...
Client
192.168.1.50:54321
│
│ connects to
▼
192.168.1.20:8080
↓
ESTABThe client normally receives an ephemeral source port such as 54321, while the server uses its well-known or configured listening port.
ss: The Main Tool For Inspecting Connections
The command you’ll use for socket inspection is:
ssss stands for socket statistics.
It replaces many of the older socket-inspection patterns that administrators historically used with netstat.
The basic command:
ssshows active socket information.
However, the real value comes from combining its options to answer specific questions.
ss Patterns And Options
This is where ss becomes genuinely useful for administration. You don’t need to memorize every option, but you should become comfortable constructing a command based on the question you’re trying to answer.
Show Listening Sockets
Start with:
ss -lThis shows listening sockets.
For TCP listeners, a more useful pattern is:
ss -ltThe options mean:
-l listening
-t TCPSo:
ss -ltmeans:
Show TCP sockets that are listening.
You may see something like:
State Local Address:Port
LISTEN 0.0.0.0:22
LISTEN 127.0.0.1:631
LISTEN [::]:22This immediately answers:
“What TCP ports are accepting connections on this machine?”
Show UDP Listeners
For UDP:
ss -luHere:
-l listening
-u UDPUnlike TCP, UDP doesn’t establish connections in the same way, so don’t expect the same LISTEN/ESTAB lifecycle.
Show Both TCP And UDP
You can combine the protocol options:
ss -ltuThis is one of the most useful quick checks on a server.
It answers:
“What TCP and UDP sockets are currently listening?”
Show Process Information
A port number tells you that something is listening, but administrators usually want the next piece of information:
“Which process owns this socket?”
Use:
sudo ss -lntupThe important options are:
-l listening
-n numeric addresses and ports
-t TCP
-u UDP
-p process informationYou may see:
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=812,fd=3))The process information connects networking back to the process model.
Port
↓
Socket
↓
ProcessThis is extremely useful when you discover an unexpected listening port.
Note
Process information can require elevated privileges. If ss -lntup doesn’t show the process details you expect, try it with sudo.
Avoid Resolving Names
By default, some networking tools may attempt to turn addresses and ports into names.
For administration, numeric output is often preferable because it is faster and less ambiguous:
ss -nFor example:
192.168.1.20:8080is easier to reason about than output that tries to replace addresses with hostnames.
This is especially useful during troubleshooting because you don’t want your socket-inspection command itself waiting on DNS resolution.
A common pattern is therefore:
ss -lntrather than:
ss -ltwhen you want compact, numeric TCP listener information.
Show Established TCP Connections
To focus on active TCP connections:
ss -tnThis shows TCP sockets using numeric addresses.
To specifically show established connections:
ss -tn state establishedThis is useful when you want to answer:
“Which TCP connections are currently active?”
You might see:
ESTAB 192.168.1.20:22 192.168.1.50:53421
ESTAB 192.168.1.20:443 192.168.1.60:41230Now you can see both sides of the connection.
Show Everything
A broad inspection command is:
ss -antThis includes TCP sockets rather than only listeners.
For both TCP and UDP:
ss -anutThe exact command you choose should depend on the question you’re asking. Starting with a huge output dump and then trying to understand it is usually less useful than filtering toward the specific state or protocol you care about.
Show Listening Sockets With Numeric Output And Processes
One command worth becoming comfortable with is:
sudo ss -lntupIt gives you a practical overview of listening TCP and UDP sockets:
listen
+ numeric
+ TCP
+ UDP
+ processThis is often the first command worth running when you inherit a Linux server and want to understand what network services are exposed.
Filter By Port
You can filter socket output using expressions.
For example:
ss -lnt 'sport = :8080'This asks for TCP listening sockets whose source port is 8080.
For a destination port:
ss -tn 'dport = :443'The distinction between sport and dport matters when inspecting established connections.
For a listening server socket, the local port is generally the service’s listening port, so filtering on sport is often what you want.
Filter By Address
You can also filter by local address.
For example:
ss -lnt 'src 127.0.0.1'This can help determine whether a service is listening only on loopback.
Similarly:
ss -lnt 'src 0.0.0.0'can show IPv4 listeners bound to all local IPv4 addresses.
The exact filter syntax is more powerful than you’ll normally need, but the pattern is worth learning:
ss [display options] 'filter expression'This lets you ask a focused question instead of manually searching through a large output.
Why 0.0.0.0 And 127.0.0.1 Matter
Consider:
127.0.0.1:8080versus:
0.0.0.0:8080A service bound to 127.0.0.1 accepts connections through the loopback interface only.
That means a remote machine cannot normally connect to it.
A service bound to 0.0.0.0 is listening on all local IPv4 interfaces.
For example:
127.0.0.1:8080means roughly:
this machine onlywhile:
0.0.0.0:8080means:
all local IPv4 interfacesThis distinction is one of the most common causes of confusion when a service works locally but cannot be reached from another machine.
IPv6 Listeners
You may also encounter:
[::]:8080This is an IPv6 wildcard address.
It is the IPv6 equivalent of listening on all IPv6 interfaces.
Don’t confuse:
0.0.0.0:8080with:
[::]:8080They belong to different address families.
Whether an IPv6 wildcard listener also accepts IPv4 connections can depend on the application’s socket configuration and the system’s IPv6 behavior, so don’t assume that seeing [::]:8080 automatically means IPv4 clients can connect.
A Practical ss Decision Tree
Instead of memorizing random combinations, start with the question.
What is listening?
→ ss -lntup
Which TCP connections are active?
→ ss -tn state established
What is listening on port 8080?
→ ss -lnt 'sport = :8080'
Which process owns the listener?
→ sudo ss -lntup
Do I need numeric addresses?
→ add -nThe important skill is constructing the command from the information you need.
nc: Creating And Testing Connections
nc, commonly called netcat, is a small networking utility that can create TCP or UDP connections and can also listen for incoming connections.
It is useful because it lets you test the network without needing a full application server.
For example, start a TCP listener:
nc -l 8080On another terminal, connect to it:
nc 127.0.0.1 8080Now type something in the client terminal:
helloThe listener terminal should receive:
helloYou’ve just created a real TCP connection between two processes on the same machine.
The flow is:
nc client
127.0.0.1:ephemeral-port
│
│ TCP
▼
nc listener
127.0.0.1:8080The exact nc options can vary slightly between implementations, so if your Debian installation behaves differently, check:
nc --helpThe networking concept remains the same.
Watch The Connection With ss
The nc example becomes much more useful when you inspect it from another terminal.
Start the listener:
nc -l 8080Then connect:
nc 127.0.0.1 8080While the connection is active, run:
ss -tnYou should see an established connection involving port 8080.
You may see something conceptually like:
ESTAB 127.0.0.1:8080 127.0.0.1:43122
ESTAB 127.0.0.1:43122 127.0.0.1:8080The exact output depends on the implementation and system, but the important relationship is:
Listener process
│
▼
Listening socket
│
▼
TCP connection
│
▼
Client socket
│
▼
Client processThis is the connection between the process and network layers that administrators need to understand.
Build A Tiny Python TCP Server
Now let’s replace nc with a tiny Python program.
Create:
server.pywith:
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen()
print("Listening on 127.0.0.1:8080")
connection, address = server.accept()
print(f"Connection from {address}")
data = connection.recv(1024)
print(f"Received: {data.decode()}")
connection.sendall(b"Hello from the server\n")
connection.close()
server.close()Run it:
python3 server.pyYou should see:
Listening on 127.0.0.1:8080The Python process has now created a TCP listening socket.
Check it from another terminal:
ss -lntpYou should find a listener on:
127.0.0.1:8080and, with sufficient privileges, the process information will identify Python as the owner.
What Did The Python Program Actually Do?
The important lines are:
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)This creates an IPv4 TCP socket.
Then:
server.bind(("127.0.0.1", 8080))associates the socket with:
127.0.0.1:8080Then:
server.listen()changes the socket into a listening socket.
Finally:
connection, address = server.accept()waits for a client to connect.
The sequence is:
socket()
↓
bind()
↓
listen()
↓
accept()
↓
established connectionYou don’t need to become a Python networking programmer here. The example exists to demonstrate what the operating system sees when an application opens a network service.
Connect To The Python Server
While the Python program is waiting at:
server.accept()connect to it from another terminal:
nc 127.0.0.1 8080The Python program should print something similar to:
Connection from ('127.0.0.1', 43122)
Received: helloif you type:
hellointo the nc terminal.
It will then send:
Hello from the serverback to the client.
Now you can see the entire chain:
Python process
│
▼
TCP socket
│
▼
127.0.0.1:8080
│
▼
Linux kernel
│
▼
nc clientThis is much closer to what a real network service does.
What Does The Client Port Mean?
You may have noticed that the client had a port such as:
43122You did not specify that port.
The operating system selected an ephemeral port for the outgoing connection.
So the connection looks like:
Client
127.0.0.1:43122
│
│ TCP
▼
Server
127.0.0.1:8080The server has a predictable listening port.
The client generally receives a temporary source port so that many simultaneous connections can coexist.
For example:
192.168.1.50:43122 → 192.168.1.20:8080
192.168.1.50:43123 → 192.168.1.20:8080
192.168.1.50:43124 → 192.168.1.20:8080All three can connect to the same server port because their client-side endpoints differ.
A TCP Connection Is Identified By Endpoints
A useful simplified model for a TCP connection is:
source IP
source port
destination IP
destination portFor example:
192.168.1.50:43122
↓
192.168.1.20:8080Another client can connect:
192.168.1.60:51234
↓
192.168.1.20:8080Both are connections to the same server port, but they are different connections because their source endpoints differ.
This is why a server can have hundreds or thousands of simultaneous clients connected to one listening port.
Listening Port Vs Established Connection
This distinction is worth seeing directly.
Suppose Python is listening on:
127.0.0.1:8080Before a client connects:
LISTEN
127.0.0.1:8080After a client connects:
LISTEN
127.0.0.1:8080
ESTAB
127.0.0.1:43122
↕
127.0.0.1:8080The listening socket doesn’t simply disappear when a connection arrives. The server continues listening for additional clients while established connection sockets represent individual clients.
Conceptually:
┌── Client A
│
Listening socket ├── Client B
│
└── Client CThis is one reason ss output can contain many entries for the same local port.
Why A Service Can Work Locally But Not Remotely
Now we can explain a common Linux networking problem.
Suppose your Python server uses:
server.bind(("127.0.0.1", 8080))On the same machine:
nc 127.0.0.1 8080works.
But another machine cannot connect to:
192.168.1.20:8080Why?
The service is bound only to loopback.
The listening socket is:
127.0.0.1:8080not:
0.0.0.0:8080If you change the example to:
server.bind(("0.0.0.0", 8080))the service listens on all local IPv4 interfaces.
Now the machine’s actual network address can be used to reach it.
This is a very common distinction when diagnosing services.
A Practical ss Investigation
Imagine somebody reports:
“Port 8080 isn’t working.”
Don’t immediately assume the application is broken.
Start with:
sudo ss -lntp 'sport = :8080'There are several possible outcomes.
Nothing Is Listening
If nothing is returned, there may simply be no service listening on that port.
Check the application or service that is supposed to provide it.
Something Is Listening On Loopback
You might see:
127.0.0.1:8080The service exists, but it is reachable only locally.
Something Is Listening On All IPv4 Interfaces
You might see:
0.0.0.0:8080Now the service is potentially reachable through the machine’s IPv4 interfaces, assuming routing and other controls allow the connection.
Something Is Listening On A Specific Address
You might see:
192.168.1.20:8080The service is bound specifically to that address.
This can be intentional, especially on machines with multiple interfaces.
The point is that ss lets you distinguish these cases immediately.
Ports Are Not Security Boundaries
A listening port means a process has made itself available through a network socket.
It does not mean the service is necessarily reachable from every network.
For example:
Process
↓
Listening on 0.0.0.0:8080
↓
Routing / interface
↓
Other network controls
↓
Remote clientWhether a remote client can actually connect depends on the complete path.
This is why “the port is listening” and “the port is reachable” are different statements.
We will use this distinction heavily when we reach the networking debugging topic.
Useful ss Commands To Remember
You don’t need to memorize the entire ss manual. These patterns cover a large part of everyday administration:
ss -lntShow TCP listeners using numeric addresses and ports.
ss -lntupShow listening TCP and UDP sockets with numeric output and process information.
ss -tn state establishedShow established TCP connections.
ss -antShow TCP sockets, including non-listening states.
ss -lnt 'sport = :8080'Show a TCP listener on port 8080.
sudo ss -lntupShow listening sockets and the processes that own them.
The pattern to internalize is:
ss
│
├── what protocol?
│ ├── -t TCP
│ └── -u UDP
│
├── what state?
│ └── -l listening
│
├── how much detail?
│ ├── -n numeric
│ └── -p process
│
└── what subset?
└── filter expressionCleanup
If you created the Python example, stop it with:
Ctrl+CIf you started an nc listener, stop it with:
Ctrl+CThe server.py file can be removed when you’re finished:
rm server.pyWhat You Should Remember
A network service is not just “a port.”
The useful mental model is:
Process
↓
Socket
↓
IP address + port
↓
Transport protocol
↓
Networkss lets you inspect the kernel’s view of those sockets.
nc gives you a lightweight way to create and test network connections.
The Python example shows how an ordinary process can create a socket, bind it to an address and port, listen for connections, and accept a client.
Most importantly, when you see something like:
0.0.0.0:8080you should immediately think:
A process is listening on TCP port 8080 across the machine’s IPv4 interfaces.
And when you see:
127.0.0.1:8080you should think:
The service is listening on port 8080, but only through the local loopback interface.
Those two observations alone solve a surprising number of real-world “the service is running but I can’t connect” problems.
What’s Next
We’ve now connected processes to network sockets and ports. The next topic stays short and simple ways to manage your wireless (WiFi) connection. It can be handy — the nmcli tool.