Frontend Labs
⋯

Part VI — The Transport Layer: UDP

21. Ports, sockets, and the connection 4-tuple

21.1 Ports

A port is a 16-bit number (0–65535) that identifies which application endpoint on a host a segment belongs to. It's the demultiplexing key at Layer 4.

Range Name Use
0–1023 Well-known / system Binding usually needs root/CAP_NET_BIND_SERVICE. 22 SSH, 53 DNS, 80 HTTP, 443 HTTPS, 123 NTP, 25 SMTP.
1024–49151 Registered App vendors register these (3306 MySQL, 6379 Redis, 5432 Postgres). Not enforced.
49152–65535 Dynamic / ephemeral (IANA) Source ports for outbound connections. Linux actually uses net.ipv4.ip_local_port_range, default 32768 60999.

21.2 The socket and the 4-tuple

A TCP connection is uniquely identified by the 4-tuple:

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

Plus the protocol, it's a 5-tuple. The kernel uses it as the hash key to find the right connection for every incoming segment.

Consequences:

  • One server :443 can hold millions of simultaneous connections — they differ in the client IP/port, not the server side.
  • A single client can open at most ~28 000 connections to the same (dst IP, dst port) before exhausting its ephemeral range — then connect() returns EADDRNOTAVAIL. More client IPs, or more destination IPs/ports, or SO_REUSEADDR/IP_BIND_ADDRESS_NO_PORT extend this.
  • SO_REUSEPORT: multiple listening sockets (one per worker thread/process) can bind the same port; the kernel load-balances incoming connections across them. This is how nginx/HAProxy/Envoy scale across cores without a single accept lock.

21.3 Listening vs connected sockets

$ ss -tanp
State    Recv-Q Send-Q  Local Address:Port   Peer Address:Port
LISTEN   0      511     0.0.0.0:443          0.0.0.0:*          # accepts new conns
ESTAB    0      0       10.0.0.5:443         203.0.113.9:54321  # one client
ESTAB    0      0       10.0.0.5:443         203.0.113.9:54322  # same client, diff port
  • LISTEN socket: bound to (local IP or wildcard, port). Send-Q here = the accept queue (backlog) capacity; Recv-Q = completed connections waiting for accept().
  • Each accept() returns a new socket bound to the full 4-tuple.
  • backlog (from listen(fd, backlog)) is capped by net.core.somaxconn (default 4096 on modern Linux). If it overflows, new SYNs are dropped → clients retry after 1 s → visible "sometimes takes 1s to connect."

21.4 War story

A payments service capped out at ~28 000 req/s to its downstream fraud API and threw cannot assign requested address. It opened a fresh connection per request to a single fraud-api:443 VIP, exhausting ephemeral ports faster than TIME_WAIT (60 s) cleared them. Fix: an HTTP connection pool with keep-alive (reuse a few hundred connections). Throughput went to 200 k req/s. Lesson: "connection per request" doesn't scale; the 4-tuple space to a single destination is only ~28k wide.

Try it yourself

sysctl net.ipv4.ip_local_port_range net.core.somaxconn
ss -s                                    # summary: sockets by state
ss -tan state time-wait | wc -l          # how many TIME_WAIT
ss -tlnp                                  # listening TCP sockets + owning process
cat /proc/sys/net/ipv4/tcp_max_syn_backlog

Generated from real-life-example.md · 8 Oct 2026 16:36