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— IPv4AF_INET6— IPv6AF_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_rangeandnet.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)andbind(127.0.0.1)are the same.” —0.0.0.0listens on every interface (reachable from outside);127.0.0.1only 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_REUSEADDRvsSO_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.1TCP?” — no IP/TCP stack, no port lookup, no checksums. Used for local IPC like Gunicorn ↔ nginx.