How Keep‑Alive Works in HTTP and TCP: Deep Dive into Nginx, Node.js and Linux
This article examines the keep‑alive mechanism at both the HTTP and TCP layers, explains how Nginx parses and processes the Connection header, dissects Linux kernel socket options and libuv implementation, and shows how to configure and debug keep‑alive to release idle connections efficiently.
Introduction
The article explores why long‑lived connections can improve efficiency but also waste resources when the peer disappears, and it investigates how to detect dead connections using keep‑alive mechanisms in HTTP and TCP.
1. HTTP Layer Keep‑Alive
When a client sends
Connection: keep-alive, Nginx keeps the connection open for a configurable period and limits the number of requests per connection.
<code>keepalive_timeout timeout;
keepalive_requests number;</code>Nginx reads the request header with
ngx_http_read_request_header, parses the request line, and extracts the
Connectionheader. The header is stored in
r->header_in. Based on the header value, Nginx sets
r->headers_in.connection_typeto either
NGX_HTTP_CONNECTION_CLOSEor
NGX_HTTP_CONNECTION_KEEP_ALIVE, and later the
keepaliveflag is set accordingly.
<code>static void ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, unsigned offset) {
if (ngx_strcasestrn(h->value.data, "close", 5)) {
r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
} else if (ngx_strcasestrn(h->value.data, "keep-alive", 10)) {
r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
}
}</code>After processing the request,
ngx_http_finalize_connectiondecides whether to close the socket or start a keep‑alive timer with
ngx_http_set_keepalive.
<code>if (r->keepalive && clcf->keepalive_timeout > 0) {
ngx_http_set_keepalive(r);
}</code>2. TCP Layer Keep‑Alive
TCP provides three configurable parameters: idle time before probes, interval between probes, and the maximum number of probes. Linux defaults are defined as:
<code>#define TCP_KEEPALIVE_TIME (120 * 60 * HZ) // two hours
#define TCP_KEEPALIVE_PROBES 9
#define TCP_KEEPALIVE_INTVL (75 * HZ)</code>These values correspond to
TCP_KEEPIDLE,
TCP_KEEPINTVL, and
TCP_KEEPCNTsocket options.
3. Nginx Implementation of Keep‑Alive
Nginx uses the socket option
SO_KEEPALIVEto enable the TCP keep‑alive mechanism. When enabled, it also sets the TCP‑level options via
setsockoptif the application provides values.
<code>int uv__tcp_keepalive(int fd, int on, unsigned int delay) {
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)))
return UV__ERR(errno);
if (on && setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &delay, sizeof(delay)))
return UV__ERR(errno);
return 0;
}</code>In the Linux kernel,
sock_setsockopthandles
SO_KEEPALIVEby calling
tcp_set_keepalive, which updates
tp->keepalive_timeand starts a timer if the socket is in an established state.
<code>if (sock_flag(sk, SOCK_KEEPOPEN) &&
!(TCPF_CLOSE | TCPF_LISTEN) & (1 << sk->sk_state)) {
tp->keepalive_time = val * HZ;
tcp_reset_keepalive_timer(sk, keepalive_time_when(tp));
}</code>The timer callback
tcp_keepalive_timerchecks how long the connection has been idle. If the idle period exceeds
keepalive_time, it sends a probe. After a configurable number of unanswered probes (
keepalive_probes), it sends a reset packet and closes the connection.
<code>if (elapsed >= keepalive_time_when(tp)) {
if (tp->probes_out >= sysctl_tcp_keepalive_probes) {
tcp_send_active_reset(sk, GFP_ATOMIC);
tcp_write_err(sk);
} else {
tcp_write_wakeup(sk);
tp->probes_out++;
}
tcp_reset_keepalive_timer(sk, keepalive_intvl_when(tp));
}</code>4. Interaction with Application Data
If the socket has pending data or unacknowledged packets, the kernel prefers sending those over keep‑alive probes, which can make the heartbeat ineffective. To handle this, Linux provides
TCP_USER_TIMEOUT, which forces the connection to be closed if no ACK is received within a user‑defined interval.
<code>if (tp->user_timeout &&
(tp->packets_out > 1) &&
(tcp_time_stamp - tp->rcv_tstamp) >= tp->user_timeout)
tcp_send_active_reset(sk, GFP_ATOMIC);
</code>Setting both
SO_KEEPALIVEand
TCP_USER_TIMEOUTensures that idle connections are detected reliably even when application traffic is present.
5. Default Settings and Verification
On a typical Linux system, keep‑alive is disabled by default. The article provides a small C program that creates a socket, queries
SO_KEEPALIVE,
TCP_KEEPIDLE,
TCP_KEEPINTVL, and
TCP_KEEPCNT, and prints their values.
<code>int sockfd = socket(AF_INET, SOCK_STREAM, 0);
getsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen);
printf("keep‑alive enabled: %d\n", optval);
getsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, &optval, &optlen);
printf("idle time: %d seconds\n", optval);
getsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, &optval, &optlen);
printf("probe interval: %d seconds\n", optval);
getsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, &optval, &optlen);
printf("max probes: %d\n", optval);
</code>The output confirms that keep‑alive is off and shows the default timeout values.
6. Observing Keep‑Alive Packets
Using Wireshark, a keep‑alive probe appears as a TCP segment with the ACK flag set and no payload. The article includes screenshots of such packets.
In summary, the keep‑alive mechanism involves coordinated configuration at both the HTTP and TCP layers. Properly tuning
keepalive_timeout,
keepalive_requests,
TCP_KEEPIDLE,
TCP_KEEPINTVL,
TCP_KEEPCNT, and optionally
TCP_USER_TIMEOUTallows a server to reuse connections efficiently while avoiding resource waste caused by dead peers.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.