Skip to main content
Every time your browser loads a resource from a server, it relies on a TCP connection. Understanding how TCP connections are established — and how Keep-Alive reuses them — is fundamental to understanding web performance.

TCP connections are expensive

Before your browser can send a single byte of HTTP data, it must complete a three-way handshake with the server:
  1. SYN — The client sends a synchronization packet to the server.
  2. SYN-ACK — The server acknowledges and responds with its own synchronization.
  3. ACK — The client acknowledges the server’s response.
Only after this exchange can the actual HTTP request be sent. On a typical broadband connection this takes 20–100ms. On a mobile network or cross-continental connection, it can easily reach 200–500ms. When you consider that a modern web page makes dozens or hundreds of HTTP requests, the cumulative cost of repeatedly opening and closing connections becomes significant.
Client                          Server
  |                               |
  |-------- SYN ----------------> |
  |                               |
  | <------- SYN-ACK ------------ |
  |                               |
  |-------- ACK ----------------> |
  |                               |
  |===== Connection Ready ======= |
  |                               |
  |-------- GET /index.html ----> |
  |                               |
  | <------- 200 OK + body ------- |

What Keep-Alive means

Keep-Alive (also called a persistent connection) allows multiple HTTP requests to reuse the same TCP connection rather than tearing it down and re-establishing it each time. Instead of paying the three-way handshake cost for every resource, you pay it once and amortize it across all requests on that connection. Under HTTP/1.0, connections were closed by default after each response. HTTP/1.1 made persistent connections the default, and the Connection: keep-alive header became the mechanism to negotiate them explicitly.

How Keep-Alive headers work

When your browser connects to a server, it sends a request like this:
GET /style.css HTTP/1.1
Host: example.com
Connection: keep-alive
Keep-Alive: timeout=5, max=1000
The server responds with:
HTTP/1.1 200 OK
Content-Type: text/css
Content-Length: 4823
Connection: keep-alive
Keep-Alive: timeout=5, max=1000
The two key parameters in the Keep-Alive header are:
  • timeout — How many seconds the connection can remain idle before the server closes it. In this example, timeout=5 means the server will close an idle connection after 5 seconds.
  • max — The maximum number of requests allowed on this connection before it is closed. max=1000 means the connection can serve up to 1000 requests before being torn down.
To explicitly close a connection after a response, either party can send:
Connection: close
Set your server’s keep-alive timeout to be slightly longer than the interval at which your clients typically make follow-up requests. Too short and you lose the benefit; too long and idle connections waste server file descriptors and memory.

HTTP/1.1 persistent connections vs. HTTP/2 multiplexing

Keep-Alive under HTTP/1.1 is a significant improvement over HTTP/1.0, but it still has a fundamental limitation: requests on a connection are still sequential. The browser must wait for the current response before sending the next request on that connection (head-of-line blocking). This is why browsers open 6 parallel connections per origin — to achieve concurrency. HTTP/2 takes a different approach entirely. Rather than reusing a connection for sequential requests, HTTP/2 multiplexes multiple request streams over a single connection simultaneously:
HTTP/1.1 Keep-Alive (3 connections):

Conn 1: [req1]---[resp1][req4]---[resp4]
Conn 2: [req2]---[resp2][req5]---[resp5]
Conn 3: [req3]---[resp3][req6]---[resp6]

HTTP/2 (1 connection, multiplexed):

Conn:   [req1][req2][req3][req4][req5][req6]
        [resp3][resp1][resp5][resp2][resp4][resp6]  (any order)
If you’re serving over HTTP/2, you don’t need to tune Keep-Alive for concurrency — a single persistent connection handles it. Focus instead on ensuring your TLS and connection setup is fast, since that one connection carries all traffic.

Performance implications

Reduce connection overhead in HTTP/1.1

If you’re serving over HTTP/1.1 (for example, for clients that don’t support HTTP/2), persistent connections are essential. Without them, every image, stylesheet, and script pays the full handshake cost. With a well-configured keep-alive, subsequent requests on the same connection skip the handshake entirely.
// Node.js (http module): enable keep-alive on an outgoing agent
const http = require('http');

const agent = new http.Agent({
  keepAlive: true,
  maxSockets: 10,
  keepAliveMsecs: 3000, // Send TCP keep-alive probes every 3 seconds
});

http.get({ hostname: 'example.com', path: '/', agent }, (res) => {
  // Connection will be reused for subsequent requests
});

TCP-level keep-alive vs. HTTP-level Keep-Alive

Note the distinction between two related but separate mechanisms:
  • HTTP Keep-Alive (covered above): reuses a TCP connection across multiple HTTP requests. Configured via HTTP headers.
  • TCP keep-alive: a lower-level mechanism where the OS sends periodic probe packets on idle connections to detect if the remote side has disappeared. Configured at the socket/OS level, not via HTTP headers.
Both are often described with the same term, but they operate at different layers of the network stack.
TCP keep-alive probes are primarily a reliability mechanism — they ensure long-lived idle connections aren’t silently dropped by NAT devices or firewalls. HTTP Keep-Alive is a performance mechanism for connection reuse.

Nginx configuration example

Here is a minimal Nginx configuration that enables HTTP keep-alive with sensible defaults:
http {
    keepalive_timeout  65;     # Close idle connections after 65 seconds
    keepalive_requests 1000;   # Maximum requests per connection

    server {
        listen 443 ssl http2;
        server_name example.com;

        # ... SSL and location config
    }
}
Enable HTTP/2 (http2 on the listen directive) alongside keep-alive. HTTP/2 clients will automatically benefit from multiplexing while HTTP/1.1 clients fall back to persistent connections.

Summary

Keep-Alive is one of the most impactful and lowest-effort performance improvements available. By reusing TCP connections, you eliminate repeated handshake latency for every resource on the page. Under HTTP/1.1, this means fewer connection round-trips across many parallel connections. Under HTTP/2, it means a single connection carries all traffic with full concurrency — making Keep-Alive less of a tuning concern and more of a baseline assumption. Either way, you should ensure your server is configured to support persistent connections and that your clients are taking advantage of them.