When your server says “address already in use” or your firewall blocks an unexpected port, the root cause usually traces back to a misunderstanding of ports, sockets, and network interfaces. These three concepts are the foundation of every networked application, and you’ll debug problems much faster once the model is clear in your head.

IP Addresses and Network Interfaces

A network interface is a point of connection between a computer and a network — physical (an Ethernet card) or virtual (a loopback device, a tunnel). Each interface has an IP address.

$ ip addr show

1: lo: <LOOPBACK,UP,LOWER_UP>
    inet 127.0.0.1/8 scope host lo          # loopback — only accessible locally

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 10.0.0.5/24 scope global eth0      # private IP (AWS, VPC)
    inet 203.0.113.42/32 scope global eth0  # public IP (if directly assigned)

On a cloud instance you typically see:

  • lo — loopback (127.0.0.1) — packets never leave the machine
  • eth0 / ens3 / enp0s3 — the main network interface

A server process binds to an IP address and port. The combination of IP + port uniquely identifies where it listens.

Ports: What They Are and Who Assigns Them

A port is a 16-bit number (0–65535) that identifies a specific service or application on a host. The IP address routes packets to the right machine; the port delivers them to the right process.

Port ranges:

Range Name Who uses it
0–1023 Well-known ports System services (requires root to bind)
1024–49151 Registered ports Application-layer services
49152–65535 Ephemeral ports Kernel-assigned client ports

When your browser connects to a server, it uses a random ephemeral port as the source, and connects to port 80 or 443 on the server. The server can distinguish thousands of simultaneous browser connections because each connection has a unique source IP + source port combination.

Common Port Numbers

Port Protocol Service
22 TCP SSH
25 TCP SMTP
53 TCP/UDP DNS
80 TCP HTTP
443 TCP HTTPS
3306 TCP MySQL
5432 TCP PostgreSQL
6379 TCP Redis
8080 TCP HTTP alt (dev servers)
27017 TCP MongoDB

Sockets: Putting It Together

A socket is the software abstraction that combines an IP address, a port, and a protocol into a communication endpoint. A TCP connection is uniquely identified by a 4-tuple:

(source IP, source port, destination IP, destination port)

This is why a server can handle thousands of simultaneous connections on port 443 — each connection has a different source IP/port, making each 4-tuple unique.

Client 1: (203.0.113.10:52341, 93.184.216.34:443)
Client 2: (203.0.113.10:52342, 93.184.216.34:443)
Client 3: (172.16.5.8:63211,   93.184.216.34:443)

All three connections are to the same server port, but they’re distinct sockets.

Binding: Listening on an Address

When you start a web server and it “listens on port 3000,” it’s creating a socket and calling bind() on that port. The bind address controls which network interface the server accepts connections on.

Binding to 0.0.0.0 vs a Specific IP

0.0.0.0:3000    → accepts connections on ALL interfaces
127.0.0.1:3000  → accepts connections ONLY from localhost
10.0.0.5:3000   → accepts connections ONLY on the eth0 interface

This is security-relevant: a database or internal service should bind to 127.0.0.1, not 0.0.0.0. Binding to 0.0.0.0 exposes the service on every interface, including the public-facing one.

In your application:

# Python — restrict to localhost
server.bind(('127.0.0.1', 3000))

# Python — accept connections on all interfaces
server.bind(('0.0.0.0', 3000))
# Check what a service is bound to
$ ss -tlnp | grep 3000
LISTEN  0  128  127.0.0.1:3000   0.0.0.0:*   users:(("node",pid=12345,fd=21))

Checking What’s Listening

# List all listening TCP sockets with process names
$ ss -tlnp
State    Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN   0       128     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=1234))
LISTEN   0       128     127.0.0.1:5432      0.0.0.0:*          users:(("postgres",pid=5678))

# Same with lsof
$ lsof -i TCP -s TCP:LISTEN

# Find what's using port 8080
$ ss -tlnp | grep :8080
$ lsof -i :8080

# Show all established connections
$ ss -tnp

“Address Already in Use”

This error means another process is already bound to that port. Track it down:

$ lsof -i :3000
COMMAND   PID     USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    12345   ubuntu  21u  IPv4   98765      0t0  TCP 127.0.0.1:3000 (LISTEN)

# Kill the offending process
$ kill 12345

Sometimes the port is held by a socket in TIME_WAIT state from a recently closed connection. You can bypass this (during development only) with SO_REUSEADDR:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 3000))

Basic Firewall with ufw

Ports that are open on your process must also be allowed through the firewall:

# Allow HTTP and HTTPS
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp

# Allow SSH (before enabling ufw!)
$ sudo ufw allow 22/tcp

$ sudo ufw enable
$ sudo ufw status
Status: active
To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere

Conclusion

IP addresses identify machines, ports identify services on those machines, and sockets are the runtime instances that tie the two together for a specific connection. When you bind to 0.0.0.0, you expose a service on every interface — bind to 127.0.0.1 for anything that shouldn’t be reachable from outside the machine. Use ss -tlnp to see exactly what’s listening where, and lsof -i :PORT to find what owns a specific port when you get “address already in use.”