调大 tcp_max_syn_backlog 无法解决 SYN_RECV 堆积,因现代内核默认启用 syncookies 使其失效;真正原因是 syncookies 关闭后 accept 慢或上层工作线程/连接池耗尽导致连接卡在 SYN-RECV。

为什么 tcp_max_syn_backlog 调大后 SYN_RECV 还堆积?
单纯调高 tcp_max_syn_backlog 几乎从不解决真实 SYN_RECV 堆积问题。它只控制「尚未完成三次握手的连接在 SYN queue 中最多能存多少个」,而现代 Linux 内核(2.6.20+)默认启用 tcp_syncookies=1,此时该参数实际被忽略——SYN queue 溢出时直接启用 syncookie,不会丢包,也不会让 ss -s 显示大量 SYN-RECV。真正导致你看到大量 SYN-RECV 的,往往是 syncookie 被禁用,或连接卡在更下游环节。
net.ipv4.tcp_syncookies 关闭时的连锁效应
一旦 tcp_syncookies=0,SYN queue 溢出就真会丢包,且内核会把已分配但未完成握手的 socket 一直挂在那里,表现为持续增长的 SYN-RECV 状态。这时 tcp_max_syn_backlog 才起作用,但它只是“上限”,不是“缓冲池大小”——实际队列长度还受 listen() 的 backlog 参数和 net.core.somaxconn 双重限制:
-
listen(sockfd, backlog)中的backlog值会被内核截断为min(backlog, net.core.somaxconn) -
net.core.somaxconn又必须 ≥tcp_max_syn_backlog,否则后者无效(内核日志会 warn “TCP: Invalid max_syn_backlog”) - 即使三者都调高,若应用层 accept() 太慢(比如阻塞在 DB 查询、锁竞争、GC 暂停),SYN queue 仍会填满
真正卡住连接的常见下游瓶颈
很多运维只盯着 TCP 参数,却忽略了连接其实在更上层就堵死了。以下情况会导致 socket 长期卡在 SYN-RECV:
- Nginx/Apache 的
worker_connections或MaxRequestWorkers耗尽,新连接无法被分发到 worker,accept() 不被调用 - Java 应用使用了同步 I/O 模型(如 Tomcat 默认 BIO),线程池满,
accept()调用被阻塞在锁或队列中 - eBPF 工具(如
tcplife)显示大量连接生命周期极短( - 云环境里,SLB/ALB 的健康检查探测间隔远大于后端
tcp_fin_timeout,造成连接状态错乱,部分连接被错误标记为SYN-RECV
验证和定位的最小命令集
别急着改参数,先确认当前到底卡在哪一层:
- 运行
ss -n state syn-recv sport = :80 | wc -l看真实堆积数(注意:ss -s的 summary 有统计延迟,不准确) - 检查
cat /proc/sys/net/ipv4/tcp_syncookies—— 若为 0,立刻开;若为 1,tcp_max_syn_backlog就基本无意义 - 用
cat /proc/net/netstat | grep -i "Syn"查看SynTooBig(syncookie 触发次数)和SynRetrans(重传数),若前者为 0 且后者飙升,说明 syncookie 没生效,得查tcp_syncookies和net.ipv4.conf.all.rp_filter(反向路径过滤可能丢 SYN-ACK) - 对监听进程做
strace -p $(pgrep nginx) -e trace=accept,accept4 2>&1 | head -20,看是否长期无输出——说明 accept 调用根本没执行
真正难排查的,是 accept() 调用本身没阻塞,但 socket 创建后立刻被内核回收(因 SO_LINGER 或 close() 调用过早),这种 case 在容器化环境中特别容易因 init 进程信号处理异常而触发。










