To send one HTTP request, a TCP connection must be open underneath it. How you open, reuse, and close connections is largely invisible but decides latency and throughput. Opening a fresh connection per request is expensive, so modern HTTP has evolved to reuse connections as much as possible.
The TCP connection lifecycle
A TCP connection begins with a 3-way handshake (SYN → SYN-ACK → ACK), carries data, and closes with a 4-way teardown (FIN/ACK exchange). Over HTTPS, a TLS handshake stacks on top. So before the very first request even goes out, TCP round trips plus TLS round trips add up — for a distant server, hundreds of milliseconds can pass before any data arrives. Letting many requests share that fixed cost is the whole motivation for connection reuse.
Keep-alive and persistent connections
HTTP/1.0 closed the connection after one request by default. HTTP/1.1 flipped this, making persistent connections the default: one opened connection serves many requests back to back. You only send Connection: close when you explicitly want to close it. This pays the handshake cost once and lets later requests go out immediately over the reused connection.
GET /page1 HTTP/1.1
Host: example.com
Connection: keep-alive
HTTP/1.1 200 OK
Keep-Alive: timeout=5, max=100
Connection: keep-alive
Connection pooling
Browsers and HTTP client libraries keep open connections in a pool and reuse them for the next request to the same origin. In HTTP/1.1 a connection handles one request at a time, so for parallelism a client opens several (conventionally about six) connections per host. In HTTP/2 and HTTP/3, multiplexing lets a single connection carry many requests in parallel, so one connection per origin usually suffices.
The HTTP/1.1 pipelining trap
Pipelining is an HTTP/1.1 feature that sends multiple requests without waiting for each response. It seems like it should cut latency, but because the server must respond in the order requests arrived, a slow early request blocks fast later responses — head-of-line blocking. Worse, intermediary proxies implemented it inconsistently, so it broke in the wild. For these reasons browsers effectively disabled pipelining, and true parallelism was handed to HTTP/2 multiplexing instead.
When connections get reused, and timeouts
Connections aren't reused forever; several conditions must hold.
- Same origin: scheme, host, and port must match to reuse; over HTTPS the same TLS parameters are also required.
- Idle timeout: both server and client close idle connections after a while (
Keep-Alive: timeout=…). Past that, the next request must open a new connection. - Max requests: some servers cap requests per connection, like
max=100. - Race conditions: if the client sends just as the server is closing a connection, the request can fail — so clients should auto-retry on a new connection only for idempotent requests.
Connection: close: whichever side sends it closes the connection after that response.
Ultimately, good connection management is about spreading the fixed handshake cost across as many requests as possible. HTTP/1.1 does it with keep-alive and multiple connections; HTTP/2 and HTTP/3 do it with a single multiplexed connection. If you build a client, explicitly configuring pool size, idle timeout, and retry policy is the key to both performance and reliability.