← back

TCP keepalive, revisited

April 4, 2026

Every two years I have to look this up again so I'm just going to write it down. The three knobs that matter on Linux are tcp_keepalive_time, tcp_keepalive_intvl, and tcp_keepalive_probes.

What the defaults mean

Default Linux: 7200 / 75 / 9. So the kernel sends the first probe after 2 hours of silence, then 8 more at 75 second intervals, then gives up. Total time-to-death of an idle connection: ~2h 11m. For modern apps behind NAT and stateful firewalls, that's an eternity — most middleboxes drop idle flows after 5 to 15 minutes.

What I usually set

net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 4

So: first probe after 2 minutes idle, 4 more at 15s, total ~3 minutes to detect a dead peer. Aggressive enough to keep NATs happy, not so aggressive that a transient 30s blip kills the connection.

Per-socket overrides

Sysctls are the system-wide default. If a specific app needs different behavior, set it on the socket:

setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &120, sizeof(int));
setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &15, sizeof(int));
setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &4, sizeof(int));

Application-level keepalives are usually better

If your protocol has a ping frame (WebSocket, gRPC, HTTP/2) — use that. Application-level keepalives let you measure RTT and react in code, and they don't depend on the TCP stack agreeing with you about what "alive" means.

The one thing that always trips me up

On macOS the sysctls are named differently. net.inet.tcp.keepidle takes milliseconds, not seconds. I have lost time to this more than once.