Your PostgreSQL connection pool suddenly returns "server closed the connection unexpectedly". Your WebSocket client reconnects every ten minutes. Your gRPC API works locally, but in production behind a load balancer long streams drop with no application log.
The culprit is not always a code bug. Often an intermediate device killed an idle TCP session, and nobody noticed — until the next write.
Idle timeout: who cuts, and when
| Layer | Typical idle timeout | Consequence |
|---|---|---|
| NAT box / 4G | 30 s – 5 min | "Zombie" connection on app side |
| Cloud load balancer | 60 s – 3500 s | Silent mid-stream cut |
| Stateful firewall | Variable | Drop without clean RST |
| DB server | hours | Less common by default |
With no traffic, the session vanishes from the intermediary state table. Your process still believes it is connected.
Keepalive exists to detect early that the path is dead — not to replace an architecture that maintains useful traffic.
Kernel-level TCP keepalive
Linux sends empty TCP probes after idle time (tcp_keepalive_time, default ~7200 s — often too long for the web).
Useful settings:
sysctl -w net.ipv4.tcp_keepalive_time=600
sysctl -w net.ipv4.tcp_keepalive_intvl=30
sysctl -w net.ipv4.tcp_keepalive_probes=5
Enable SO_KEEPALIVE on critical server sockets (PostgreSQL tcp_keepalives_*, Redis, proxies).
Limit: keepalive does not always survive HTTP proxies that terminate TCP. Behind nginx, also set proxy_read_timeout and proxy_send_timeout.
Application heartbeat: when TCP is not enough
For WebSocket, SSE or DB connections via pooler:
- WebSocket: RFC 6455 ping/pong or periodic JSON (< NAT interval).
- ORM pools:
pool_pre_ping(SQLAlchemy), validation query on checkout. - Message queues: AMQP heartbeat, regular consumer ack.
Calibrate interval below the shortest timeout in the chain (NAT < LB < app).
Load balancer and cloud host
Managed offerings expose fixed timeouts:
- AWS ALB idle: up to 4000 s configurable.
- nginx default: 60 s on proxy.
- Cloudflare: 100 s on classic HTTP connections.
Read your layer's docs — then tune keepalive + heartbeat accordingly. Open a host support ticket if default timeout conflicts with long flows.
The climax: an "open" connection may already be dead
Final robustness lives in your retry and reconnection logic.
Decide and move forward without blind spots
- Map timeouts for NAT, LB, proxy and DB on one diagram.
- Enable SO_KEEPALIVE + adapted sysctl on servers.
- Add application heartbeat for WebSocket and long-lived pools.
- Load-test with deliberate idle connections.
Compare hosts and network limits in our directory.
Frequently asked questions
What is the difference between TCP keepalive and application heartbeat?
Keepalive = kernel probes for dead peer. Heartbeat = business message that can also measure latency or refresh sessions.
Why do DB connections drop after 5 or 15 minutes?
NAT, firewall or load balancer closes idle sessions; client thinks connection is open.
Which Linux parameters to tune?
tcp_keepalive_time, tcp_keepalive_intvl, tcp_keepalive_probes — lower time for short intermediary timeouts.
Does keepalive use a lot of bandwidth?
No — a few bytes per probe; avoid aggressive intervals on thousands of connections.
A useful connection is not one that stays open in a table — it is one that survives silent NATs between real requests.
