backend / networking / 06_ports_sockets.md

Ports and Sockets

5 interview angles 4 min read source

Ports and Sockets

A port is a 16-bit number (0–65535) that lets one IP host multiplex many connections. A socket is the OS handle for one end of a connection, identified by (protocol, local_ip, local_port, remote_ip, remote_port).

Port ranges

Range Name Use
0–1023 well-known / privileged requires root on Linux to bind; HTTP (80), HTTPS (443), SSH (22), DNS (53), Postgres (5432)
1024–49151 registered reserved by IANA for specific apps; in practice anyone uses them (e.g. 8080 for HTTP, 6379 Redis, 5672 RabbitMQ)
49152–65535 dynamic / ephemeral OS picks one as the client-side port for outbound connections

Linux ephemeral range is configurable: cat /proc/sys/net/ipv4/ip_local_port_range. Defaults are typically 32768–60999 — gives ~28k ports per outbound destination tuple.

The 4-tuple identifies a connection

A TCP connection is uniquely identified by (local_ip, local_port, remote_ip, remote_port). So a single server on port 443 can have hundreds of thousands of simultaneous connections — each is a different remote tuple.

Server 10.0.0.5:443 ↔ Client 198.51.100.10:54321
Server 10.0.0.5:443 ↔ Client 198.51.100.10:54322   ← different connection
Server 10.0.0.5:443 ↔ Client 198.51.100.11:54321   ← different connection

This is why “I can’t open more than 65k connections to my server” is wrong — the limit is per remote tuple, not per server port.

Common ports cheatsheet

Port Service
22 SSH
25, 587, 465 SMTP (and submission/SMTPS)
53 DNS (UDP and TCP)
80 HTTP
110, 995 POP3 / POP3S
143, 993 IMAP / IMAPS
443 HTTPS
3306 MySQL
5432 PostgreSQL
6379 Redis
5672, 15672 RabbitMQ (AMQP, management UI)
9092 Kafka
27017 MongoDB
8080, 8000 dev HTTP servers (Python’s http.server, Django runserver)

Memorize the top half — interviewers love asking “what port does Postgres listen on?”

Sockets in Python

import socket

# TCP server
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", 9000))
srv.listen(128)
conn, addr = srv.accept()
data = conn.recv(4096)
conn.sendall(b"hello")

# TCP client
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(("api.example.com", 443))

Address families:

  • AF_INET — IPv4
  • AF_INET6 — IPv6
  • AF_UNIX — Unix domain sockets (no network — IPC between local processes via a filesystem path)

Socket types:

  • SOCK_STREAM — TCP (reliable, ordered, byte stream)
  • SOCK_DGRAM — UDP (datagrams)
  • SOCK_RAW — raw IP (rarely needed, requires root)

SO_REUSEADDR vs SO_REUSEPORT

These get confused constantly.

Option Effect
SO_REUSEADDR restart your server immediately after a crash without waiting for TIME_WAIT to clear; lets you bind to a port still in TIME_WAIT
SO_REUSEPORT multiple processes can bind to the same (IP, port); kernel load-balances incoming connections across them

SO_REUSEPORT is how Gunicorn, nginx, and Envoy run multiple worker processes all listening on :80 without a parent dispatching connections.

srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)  # Linux only

Unix domain sockets

For local IPC, faster than TCP loopback (no network stack):

s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.bind("/tmp/myapp.sock")
s.listen()

Use cases: Gunicorn ↔ nginx, Postgres unix-socket connection (host=/var/run/postgresql), Docker daemon, gRPC local communication. Faster than TCP and not exposed to the network.

Ephemeral port exhaustion

Each outbound connection consumes one ephemeral port for the lifetime of the connection (and ~60s after close, in TIME_WAIT). If your service makes thousands of short outbound connections to the same destination, you run out.

Symptom: requests.exceptions.ConnectionError or OSError: [Errno 99] Cannot assign requested address.

Fixes:

  • Reuse connections (requests.Session, httpx.AsyncClient).
  • HTTP keep-alive (default in modern clients).
  • Tune net.ipv4.ip_local_port_range and net.ipv4.tcp_tw_reuse=1.
  • For NAT’d egress (cloud), more critical — see 04_nat.md.

File descriptor limits

Every socket is a file descriptor. The OS caps how many a process can open (ulimit -n, default often 1024). A web server expecting 10k connections needs ulimit -n 65535.

Port scanning ethics + tools

nmap -p 80,443,22 example.com    # check specific ports
nmap example.com                 # scan top 1000
ss -tlnp                         # what's listening on this host
lsof -i :8000                    # what process owns this port

Don’t scan hosts you don’t own — depending on jurisdiction, it’s illegal or terms-of-service-violating.

Common interview confusions

  • “A server on port 443 can only handle 65k connections.” — wrong. The server has one local port; clients have unique remote tuples. Limit is FDs, RAM, and CPU, not ports.
  • bind(0.0.0.0) and bind(127.0.0.1) are the same.”0.0.0.0 listens on every interface (reachable from outside); 127.0.0.1 only on loopback (local processes only). Big security difference.
  • “Port 0 is invalid.”bind(("", 0)) lets the OS pick an unused ephemeral port. Useful for tests.
  • “Unix sockets are slower than TCP because they’re files.” — they’re faster — no IP/TCP stack, no checksum, no port lookup.

Interview angle

  • “Difference between well-known, registered, and ephemeral ports?” — 0–1023 privileged (need root to bind on Linux), 1024–49151 IANA-registered, 49152–65535 dynamic (used as client-side ephemeral ports).
  • “What identifies a TCP connection?” — the 4-tuple (local_ip, local_port, remote_ip, remote_port). Same server port can serve millions of clients.
  • SO_REUSEADDR vs SO_REUSEPORT?” — REUSEADDR lets you re-bind a port stuck in TIME_WAIT after a crash. REUSEPORT lets multiple processes bind the same port and kernel-load-balances connections.
  • “What’s ephemeral port exhaustion and how do you fix it?” — too many short outbound connections to one destination consume all client-side ports. Fix with HTTP keep-alive / connection pooling, longer port range, tcp_tw_reuse.
  • “Why are Unix domain sockets faster than 127.0.0.1 TCP?” — no IP/TCP stack, no port lookup, no checksums. Used for local IPC like Gunicorn ↔ nginx.