tcp_max_syn_backlog 与 listen() backlog 并非同一概念:前者仅限制未完成三次握手的 SYN 队列上限,后者经内核裁剪后同时影响 SYN 队列和 accept 队列;实际 SYN 队列长度取 min(backlog, tcp_max_syn_backlog),故单调大前者无效,须同步增大 listen() 参数及 net.core.somaxconn。

tcp_max_syn_backlog 和 listen() backlog 不是同一个东西
很多人以为把 tcp_max_syn_backlog 调大,SYN 队列就自动变长了,其实它只控制「未完成三次握手的连接」在内核 SYN queue 里的上限;而真正决定应用层能接受多少待处理连接的,是调用 listen() 时传入的 backlog 参数——这个值会经过内核裁剪后,同时影响两个队列:SYN queue(半连接)和 accept queue(全连接)。tcp_max_syn_backlog 只在 listen() 的 backlog 值超过它时起约束作用。
为什么调大 tcp_max_syn_backlog 后 SYN_RECV 还堆积
根本原因是应用没同步调大 listen() 的 backlog 参数。内核会取 min(backlog, tcp_max_syn_backlog) 作为实际生效的 SYN queue 长度。比如你把 tcp_max_syn_backlog 改成 65535,但程序里仍用 listen(fd, 128),那 SYN queue 实际还是最多 128 个,超出的 SYN 包会被丢弃或触发重传,netstat -s | grep -i "SYNs to LISTEN sockets dropped" 会看到计数上涨。
- Go 默认
net.Listen("tcp", addr)底层调用listen(fd, 128) - Python 的
socket.listen(5)同理,5 是常见默认值 - Nginx 的
listen ... backlog=4096是显式覆盖,但不写就用系统默认(常为 128) -
ss -lnt看到的Recv-Q列若持续非零且接近你设的backlog,说明 accept queue 已满,新连接卡在 SYN_RECV 或直接被丢
如何确认当前生效的 backlog 值
不能只看 /proc/sys/net/ipv4/tcp_max_syn_backlog,得结合应用行为和内核裁剪逻辑来看:
- 查进程打开的监听 socket:运行
ss -lnt -o state listen | grep :端口,看Recv-Q和Send-Q——Send-Q就是当前生效的全连接队列长度(即裁剪后的backlog值) - 检查是否触发丢包:
netstat -s | grep -A 5 "SYN cookies"或cat /proc/net/netstat | grep -i "ListenOverflows\|ListenDrops" - 在高并发建连时抓包,如果客户端反复重发 SYN,服务端没回 SYN+ACK,大概率是 SYN queue 满且没开 syncookies
- 注意:Linux 4.1+ 中
tcp_max_syn_backlog对 SYN queue 的限制已弱化,更多由net.core.somaxconn主导,后者同时限制 accept queue 和裁剪listen()的backlog
调参时必须同步改的三个地方
单独改 tcp_max_syn_backlog 几乎没用,以下三项需保持合理关系: listen() backlog ≥ min(net.core.somaxconn, tcp_max_syn_backlog),且 net.core.somaxconn 通常应 ≥ tcp_max_syn_backlog。
- 修改
/proc/sys/net/core/somaxconn(推荐 ≥ 65535),它决定listen()backlog上限 - 修改
/proc/sys/net/ipv4/tcp_max_syn_backlog(可设为与somaxconn相同,如 65535) - 在代码或配置中显式增大
listen()的backlog参数:Go 用net.ListenConfig{Control: ...}+setsockopt(SOL_SOCKET, SO_BACKLOG);Nginx 加backlog=65535;Node.js 用server.listen(port, host, backlog) - 注意:某些语言 runtime(如早期 Java)会忽略传入的
backlog,硬编码为低值,需查证版本行为
tcp_max_syn_backlog 却没动 listen() 参数或 somaxconn,等于只换了门框却没换门——SYN 包照样进不来。真正卡点往往在应用层那一行 listen() 调用里,而不是 sysctl 配置文件里。










