Ports, sockets and the client-server model

What a port actually identifies, how ephemeral ports are assigned, what a listening and an accepted socket are, and the four-tuple that defines a connection.

A connection is a four-tuple

A port is not a channel; it is a demultiplexing key. The kernel identifies a connection by four values: source address, source port, destination address and destination port. Change any one and it is a different connection.

server listens on  0.0.0.0:443

connection 1  192.168.1.10:51001  ->  203.0.113.5:443
connection 2  192.168.1.10:51002  ->  203.0.113.5:443
connection 3  192.168.1.11:50000  ->  203.0.113.5:443

three distinct connections, one listening port
Port rangeNameAssigned by
0-1023Well knownIANA; binding usually needs privilege or a capability
1024-49151RegisteredIANA registry; applications may use these
49152-65535Dynamic or ephemeralThe operating system, for outgoing connections
80 / 443HTTP / HTTPSWeb traffic
22 / 25 / 53SSH / SMTP / DNSCommon infrastructure ports
3306 / 5432 / 6379MySQL / PostgreSQL / RedisDatabase defaults, often not exposed publicly

Listening, accepting, connecting

import socket

# server: bind, listen, then accept repeatedly
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("0.0.0.0", 8080))
srv.listen(128)                      # backlog: pending connections the kernel queues
print("listening on", srv.getsockname())

conn, peer = srv.accept()            # a NEW socket for this one connection
print("accepted from", peer)
conn.sendall(b"hello\n")
conn.close()

# client: each request gets a fresh ephemeral source port
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(("127.0.0.1", 8080))
print("local side", c.getsockname()) # ('127.0.0.1', 51001) and similar
print(c.recv(1024))
c.close()
  • The listening socket itself carries no application data; accept returns a new socket for each connection.
  • The backlog is finite. Once it fills, new TCP handshakes are dropped or refused, which is how a slow server causes client timeouts.
  • An ephemeral port is held for a while after the connection closes, so rapid outbound connections can exhaust the range.
  • Binding to a specific interface restricts which addresses accept traffic — 0.0.0.0 means all of them.

Port exhaustion and its symptoms

# inspect sockets on Linux
ss -tlnp                     # listening TCP sockets with owning process
ss -tan state established | wc -l
ss -s                        # a summary including TIME-WAIT counts

# count ephemeral ports in use toward one destination
ss -tan | grep '203.0.113.5:443' | wc -l

# widen the ephemeral range if the workload is outbound-heavy
sysctl net.ipv4.ip_local_port_range
SymptomLikely causeMitigation
EADDRNOTAVAIL on connectEphemeral ports exhaustedConnection pooling, wider port range
Many sockets in TIME-WAITShort-lived outbound connectionsEnable reuse, keep connections alive
Connections occasionally failListen backlog overflowingRaise the backlog, accept faster
EADDRINUSE on restartA socket is still boundSO_REUSEADDR, or wait
Latency rises with concurrencyAccept queue delay, not bandwidthAdd workers, or offload to a proxy
⚠️
A connection pool is the standard fix for outbound port exhaustion, but its size must be tuned. A pool of thousands per instance can exhaust the server's accept queue instead, which moves the failure rather than removing it.

FAQ

Can two services listen on the same port?
On the same address and protocol, no. Different address bindings, or TCP versus UDP, can coexist. SO_REUSEPORT is an explicit exception used to spread load across processes.
Why does a client source port matter?
It is what the server uses to tell concurrent connections apart. If two clients somehow used the same four-tuple, the kernel would treat them as one connection, which is why ephemeral ports are randomised.

TCP in depth: handshake, windows and congestion control IP addressing, CIDR and subnetting

Last refreshed 2026-09-18.