TCP/IP Basics Every Developer Should Understand
When you call an HTTP library in your code, a chain of protocols handles delivery, ordering, and reliability beneath you. Knowing what those protocols do — and what they cost — explains latency spikes, connection limits, and why WebSocket upgrades exist.
Published June 22, 2026Application developers can go years calling fetch() or requests.get() without ever thinking about what travels across the wire. That works until something breaks: a service is unreachable, an API times out intermittently, WebSocket connections drop after sixty seconds, or a load test reveals the server is running out of ports. At that point, a working model of the TCP/IP stack is the fastest path to a diagnosis.
The protocol stack: layers with defined responsibilities
The internet protocol suite organizes network communication into layers, each responsible for one concern. The two that matter most for application developers are IP (the Internet Protocol) and TCP (the Transmission Control Protocol).
IP handles routing: addressing (each machine has an IP address) and packet forwarding (routers inspect the destination address and forward packets toward their destination, hop by hop). IP is connectionless and unreliable in the technical sense: it delivers packets on a best-effort basis. Packets can arrive out of order, be duplicated, or be dropped. IP does not retransmit lost packets.
TCP sits on top of IP and adds the reliability layer. TCP provides a continuous, ordered, error-checked stream of bytes between two endpoints. It handles retransmission of lost packets, reordering of out-of-order packets, flow control (preventing a fast sender from overwhelming a slow receiver), and congestion control (preventing the network itself from being overwhelmed). From the application's perspective, a TCP connection looks like a pipe: bytes written at one end arrive intact at the other end, in the same order, with no duplicates.
Above TCP sits the application protocol: HTTP, FTP, SMTP, database wire protocols, and so on. Application protocols define the message format (headers, body, framing) and the request/response semantics. They rely on TCP for reliable delivery and do not reimplement it.
The TCP three-way handshake and what it costs
Before any data is exchanged, TCP requires both endpoints to agree they are ready. This is the three-way handshake:
- The client sends a SYN (synchronize) packet to the server, proposing an initial sequence number.
- The server responds with a SYN-ACK (synchronize-acknowledge), accepting the client's sequence number and proposing its own.
- The client sends an ACK (acknowledge), confirming the server's sequence number.
Only after step 3 can the client send application data. The handshake takes one full round-trip (the time for a packet to travel from client to server and back). For a client in New York connecting to a server in London, a round-trip is approximately 80 ms. Every new TCP connection starts with that 80 ms overhead before the first byte of HTTP request is sent.
This is why connection reuse matters. HTTP/1.1 introduced the Connection: keep-alive header to reuse a TCP connection for multiple requests. HTTP/2 goes further with multiplexing: multiple request/response pairs are interleaved over a single TCP connection, eliminating head-of-line blocking and the per-request connection overhead. HTTP/3 replaces TCP with QUIC, a protocol built on UDP that embeds reliability and multiplexing at the transport layer without the handshake latency TCP inherits from its 1970s design.
Ports: how the OS routes packets to the right process
An IP address identifies a machine. A port identifies a process on that machine. TCP and UDP ports are 16-bit integers (0–65535). Well-known ports are assigned by convention: 80 for HTTP, 443 for HTTPS, 22 for SSH, 5432 for PostgreSQL, 6379 for Redis. When your web server listens on port 443, the OS delivers incoming packets addressed to that port to your web server process and ignores packets for other ports.
A TCP connection is identified by four values: (source IP, source port, destination IP, destination port). The source port for an outgoing connection is typically assigned by the OS from the ephemeral port range (usually 49152–65535 on Linux). This means a single client machine can maintain up to around 16,000 simultaneous TCP connections to the same server address and port before running out of ephemeral ports. On a load test machine hitting a single server from a single IP, this limit is reachable. The fix is to use multiple source IPs, increase the ephemeral port range, or use HTTP/2 multiplexing to do more work per connection.
Connection states and TIME_WAIT
TCP connections go through a defined state machine. The states most often relevant to troubleshooting are:
- ESTABLISHED: the connection is open; data flows.
- CLOSE_WAIT: the remote side closed the connection; the local side has not yet. If many connections are stuck in CLOSE_WAIT, the application is not closing connections it has received a FIN on — typically a code bug.
- TIME_WAIT: the local side initiated the close and sent the final ACK. The OS keeps the connection in TIME_WAIT for 2 * MSL (Maximum Segment Lifetime, often 60 seconds) to absorb any delayed packets. A server under heavy load that creates and closes many connections can accumulate thousands of TIME_WAIT sockets. These are not a problem by themselves — TIME_WAIT sockets consume a kernel structure but are not file descriptors. They become a problem only if they exhaust the source port space, which is rare.
Use ss -s (Linux) or netstat -an to inspect connection state counts. A spike in CLOSE_WAIT usually points to an application-level resource leak.
How TLS sits on top of TCP
HTTPS is HTTP carried over TLS (Transport Layer Security), which is itself carried over TCP. The TLS handshake adds additional round trips after the TCP handshake. In TLS 1.2, the handshake takes two round trips; TLS 1.3 reduced this to one round trip (and supports zero round-trip resumption for returning clients with a session ticket).
The combined cost of a new HTTPS connection: one TCP handshake round-trip, plus one TLS handshake round-trip (TLS 1.3), before the first HTTP request byte. For the London example, that is 160 ms of latency before a byte of content arrives. This is why CDNs terminate TLS at edge nodes close to the user: the TCP and TLS handshake happen over tens of milliseconds to a nearby edge, and the origin request is reused over a pre-warmed connection.
Practical implications for application code
Several common application-layer decisions trace directly back to TCP behavior. Connection pooling in database libraries (PgBouncer, HikariCP, SQLAlchemy's pool) exists because opening a new TCP connection for every query would pay the handshake latency on every query. The pool maintains a fixed set of pre-established connections and leases them to requests.
HTTP keep-alive and connection pooling in HTTP client libraries (Python's requests.Session, Java's OkHttpClient) exist for the same reason. A session or client that reuses connections makes a measurable difference when calling many API endpoints sequentially.
Timeouts are the most frequently misconfigured aspect. An HTTP request has at least three different timeouts: the TCP connection timeout (how long to wait for the handshake), the response timeout (how long to wait for the first byte of response after the request is sent), and the read timeout (how long to wait between bytes of a streaming response). Setting only an overall "request timeout" misses the nuance. A server that accepts the connection immediately but then hangs before sending a response will appear to hang for the entire request timeout duration, not be caught by a short connection timeout.
Understanding TCP also clarifies WebSockets. A WebSocket connection starts as an HTTP request with an Upgrade: websocket header. The server responds with 101 Switching Protocols. At that point, the existing TCP connection is repurposed: the HTTP framing is abandoned and the WebSocket framing protocol takes over. The connection is now a persistent bidirectional channel. Load balancers and proxies that close idle HTTP connections after a timeout will also close WebSocket connections; the common workaround is a periodic ping message to keep the connection alive.