答案是排查tcp keepalive异常需四步:先用ss -ti确认连接级so_keepalive已启用,再验证内核参数是否真实生效,接着tcpdump抓包确认探测包发出与响应,最后排除nat/防火墙拦截及应用层僵死等中间干扰。

排查 TCP-Keepalive 异常,关键不是“有没有开”,而是“开了但没起作用”——多数问题出在配置未生效、探测被拦截或应用层未启用。下面分四步直击核心。
确认 Keepalive 是否真正在该连接上启用
内核参数设了 ≠ 当前连接启用了。每个 socket 必须显式调用 SO_KEEPALIVE 才会纳入探测范围:
- 用 ss -ti 查看具体连接(如
ss -ti 'dst 10.0.1.5:8080'),输出中出现keepalive字样才表示已启用;若只有timer:(keepalive,...但无keepalive关键字,说明未开启 - 检查代码:确认服务端/客户端对每个关键 socket 调用了
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)) - 注意:仅改
sysctl全局参数,而代码里没启用SO_KEEPALIVE,探测根本不会触发
验证 Keepalive 参数是否按预期加载
系统级参数可能被覆盖、未生效,或应用层自定义值未写入成功:
Linux 性能分析与调优专家,覆盖 CPU、内存、磁盘 I/O、网络、内核参数、编译优化、容器/K8s。适用场景:系统卡顿/高负载、内存不足/OOM/Swap 高、CPU 异常/iowait 高。
- 查当前生效值:
cat /proc/sys/net/ipv4/tcp_keepalive_time等三个文件,而非只看sysctl -a输出(后者可能含未加载的配置) - 若用
/etc/sysctl.d/99-xxx.conf配置,必须执行sudo sysctl --system加载;sysctl -p不保证读取所有子目录 - C语言中调
TCP_KEEPIDLE等选项时,需检查返回值:if (setsockopt(...TCP_KEEPIDLE...) == -1 && errno == ENOPROTOOPT)表示内核不支持(常见于 Alpine/musl 或旧内核)
抓包确认探测包是否发出与响应
空口说“已启用”不如亲眼看到探测帧:
- 用
tcpdump抓指定连接的保活探测:tcpdump -i eth0 'tcp[tcpflags] & tcp-ack != 0 and tcp[12] & 0xf0 == 0x50 and src host 10.0.1.5' -nn(匹配带 ACK 且无数据的保活包) - 若长时间(如超
time + probes × intvl)没看到探测包 → 应用层未启用或 socket 已关闭 - 若看到探测包发出但无响应 → 中间设备(NAT/防火墙/负载均衡)静默丢弃(尤其当
net.ipv4.tcp_timestamps=1时,某些设备会过滤无时间戳的 keepalive 包)
排除中间设备干扰和应用层误判
Keepalive 只能确认链路层可达,不能代替业务心跳:
- NAT 网关或云 LB(如 AWS ALB、阿里云 SLB)通常 5–60 分钟清理空闲连接,比默认 keepalive(2 小时)早得多;即使探测发出去,设备已删映射表,响应无法回传
- 容器网络(Cilium/Calico)或 eBPF 转发路径可能拦截或延迟 keepalive 包,需结合
bpftrace或内核日志排查 - Keepalive 成功不代表业务可用:远端进程卡死、数据库挂起、HTTP 服务僵死时,socket 仍 open 且响应探测,但请求超时 —— 这类场景必须叠加应用层心跳(如 gRPC keepalive、Redis PING)










