Every backend service you've ever deployed binds to a port, and every load test that mysteriously fails under high concurrency eventually traces back to socket or ephemeral port exhaustion. Understanding ports and sockets precisely is what separates "the server accepts connections" from actually knowing why — and it's a favorite interview topic because the naive mental model ("one port = one connection") is wrong in a way that trips up most candidates.
A port is a number, from 0 to 65,535, that tells a computer which specific program on it should receive incoming data. An address like an IP tells traffic which computer to go to; the port then tells that computer which running app — a web server, a game, an email client — should actually handle it.
A socket is one specific, ongoing conversation between two programs over the network. It's more precise than just a port number — it's the full combination of both sides' addresses and ports, which is why a single port on a server can be involved in millions of separate conversations at once without them getting mixed up.
Think of a large office building with one street address. The building's address gets mail to the right building (like an IP address), but each mail slot number — reception, accounting, HR — routes it to the right department inside (like a port). A socket is even more specific: it's like a particular phone call between a specific person in accounting and a specific customer, identified by both callers' numbers, so the switchboard can hold thousands of separate calls through the same main office line at once.
A TCP connection is uniquely identified by FIVE values, not one:
(protocol, source IP, source port, destination IP, destination port)
Client A: (TCP, 203.0.113.5, 52001, 10.0.0.20, 443)
Client B: (TCP, 198.51.100.9, 52001, 10.0.0.20, 443) <- same dest port,
different src IP
Client A again: (TCP, 203.0.113.5, 52002, 10.0.0.20, 443) <- same src IP,
new src port
All three are DISTINCT sockets on the server, all listening on the same
port 443. The OS demultiplexes incoming packets using the full 5-tuple.This is the single most important fact to internalize: a server "listening on port 443" is not a bottleneck of one connection per port. The listening socket just accepts new connections; each accepted connection becomes its own connected socket, and the 5-tuple — not the destination port alone — is what makes each one unique. A single server can hold millions of simultaneous connections on port 443 as long as it has file descriptors and memory to spare.
0 ─ 1023 Well-known / system ports (require root to bind on Unix)
80 HTTP, 443 HTTPS, 22 SSH, 53 DNS, 25 SMTP
1024 ─ 49151 Registered ports (app-specific, no root required)
3306 MySQL, 5432 PostgreSQL, 6379 Redis, 27017 MongoDB
49152 ─ 65535 Dynamic / private / ephemeral ports
the OS picks one of these automatically for the CLIENT
side of every outbound connectionOn Linux the actual ephemeral range is often tuned to something like 32768–60999 (check /proc/sys/net/ipv4/ip_local_port_range) rather than the strict IANA 49152–65535 — worth knowing because that ~28,000-port pool is finite, and it's exactly what gets exhausted when a service opens huge numbers of short-lived outbound connections without pooling or reuse.
(protocol, local IP, local port, remote IP, remote port) — it's the unique handle the OS uses to identify one specific connection, not just a port number alone.bind() on a specific port, then listen() to start accepting incoming TCP connections.connect(), which the OS pairs with an automatically assigned ephemeral local port.accept() call returns a new socket, distinct from the listening socket, scoped to that one client connection.# List all listening ports and their owning process:
lsof -i -P -n | grep LISTEN # macOS/Linux
netstat -tulpn # Linux (needs sudo for process names)
ss -tulpn # modern Linux replacement for netstat
# Check if a specific host:port is reachable (no data sent, just the TCP handshake):
nc -zv example.com 443
nc -zv localhost 5432
# Count established connections by remote address (spot connection churn / leaks):
netstat -an | grep ESTABLISHED | awk '{print $5}' | sort | uniq -c | sort -rn
# See your ephemeral port range on Linux:
cat /proc/sys/net/ipv4/ip_local_port_range
# Watch TIME_WAIT sockets pile up (a sign of high connection churn):
netstat -an | grep TIME_WAIT | wc -l
Q: If a server listens on port 443, how can it handle a million concurrent clients through one port?
A: Because a TCP connection is identified by the full 5-tuple — protocol, local IP, local port, remote IP, remote port — not by the destination port alone. The listening socket on 443 just accepts new connections; each accepted connection becomes a distinct connected socket differentiated by the client's unique IP:port combination. The OS kernel demultiplexes incoming packets to the right socket using that full tuple, so the same local port 443 is shared across as many connections as the server has resources to hold open.
Q: What causes "ephemeral port exhaustion" and how do you fix it?
A: It happens when a process opens a very large number of short-lived outbound connections faster than the OS can recycle ephemeral ports — e.g. an API gateway making a fresh TCP connection per request to a downstream service instead of reusing connections. Each connection consumes an ephemeral port until it fully closes (including its TIME_WAIT period), so under high request rate the finite pool (~28K ports on Linux by default) runs dry, and new outbound connections start failing. The fix is connection pooling and HTTP keep-alive so the client reuses existing connections instead of opening a new socket per call; at scale, you can also add more source IPs, shrink TIME_WAIT (with care — see tcp_tw_reuse), or widen the ephemeral range.
Q: What's the difference between a port and a socket?
A: A port is just a 16-bit number — one piece of addressing info. A socket is the actual OS-level handle/object representing one endpoint of a specific connection, identified by the full 5-tuple (protocol + local IP/port + remote IP/port). Conflating them is the most common mistake: "the server has a socket open on port 443" is really shorthand for "the server has many sockets, all sharing local port 443 but each pinned to a different remote IP/port."
In a "design a system that makes many downstream calls" interview (an API gateway, a BFF, a fan-out service calling dozens of microservices per request), the concrete failure mode to raise unprompted is ephemeral port and connection exhaustion on the calling side — solved with connection pooling, HTTP/1.1 keep-alive or HTTP/2 multiplexing (many logical requests over one TCP connection), and sizing pool limits against the downstream service's own capacity. It's also exactly why a load balancer or reverse proxy in front of a fleet of backend instances needs its own connection pool management to backend nodes, distinct from the client-facing connection count.
A classic interview gotcha: "if a server listens on port 443, how can it handle a million concurrent clients through one port?" — the answer is that the 5-tuple, not the port alone, defines a connection, so the same local port can be shared across many distinct sockets differentiated by remote IP/port. Another practical pitfall: services that open a huge number of short-lived outbound connections (e.g. an API gateway calling many backends) can exhaust the ephemeral port range or pile up TIME_WAIT sockets, which is why connection pooling and keep-alive matter operationally, not just for latency.