服务器出现SYN_RECV连接过多怎么办?TCP连接队列与安全排查

服务器监控突然报警"连接数飙升",登录上去 ss -tan 一看,满屏都是 SYN-RECV 状态的连接,正常业务却越来越卡——这是运维常见的一种慌乱时刻。SYN-RECV 过多可能是被 SYN Flood 攻击,也可能只是客户端握手中断、或者服务端处理太慢导致半连接队列堆积。判断错了方向,要么白白封禁正常用户,要么对着攻击毫无作为。

这篇文章讲清 SYN-RECV 的来龙去脉:先解释这个状态为什么会产生、连接队列是怎么工作的,再给出"如何判断是攻击还是负载问题"的判定方法,最后给出缓解手段(SYN Cookies、队列调优、防火墙限速)和长期防御思路。命令区分 CentOS/Rocky 与 Debian/Ubuntu 两套,并附风险提示与回滚方式。

需要强调的是,缓解 SYN-RECV 堆积不等于解决攻击。调参数只能"顶住",真正扛住大流量攻击要靠上游清洗和带宽冗余。下面的手段要按"先止血、再加固、长期靠架构"的思路组合使用。

一、SYN_RECV 是怎么产生的

TCP 建立连接要三次握手:客户端发 SYN → 服务端回 SYN-ACK 并进入 SYN-RECV 状态 → 客户端回 ACK → 服务端进入 ESTABLISHED。SYN-RECV 就是"服务端回了 SYN-ACK,但还没收到客户端最终 ACK"的中间状态。

正常情况下这个状态只持续极短时间(一个 RTT 量级)。如果服务端发出了 SYN-ACK 却迟迟收不到 ACK,会有两种情况:

  • 客户端真的没回(网络丢包、客户端崩溃、恶意攻击者故意不回);
  • 服务端根本没发出去(半连接队列已满,内核丢弃新 SYN,或回包被限速)。

半连接队列(SYN 队列)的工作机制是:每个处于 SYN-RECV 的连接都要在队列里占一个位置,队列长度由 net.ipv4.tcp_max_syn_backlog 控制。攻击者用大量伪造源 IP 的 SYN 打过来,服务端为每个都分配队列位置并发 SYN-ACK,但永远不会收到 ACK,队列很快被占满,正常用户的新 SYN 也被丢弃,服务就"打不开"了。这就是经典的 SYN Flood。

二、先判断:是攻击还是负载问题

看到一堆 SYN-RECV,别急着下结论。用下面几组证据区分:

判据 疑似攻击 疑似自身负载/网络
来源 IP 分布 大量分散、随机、很多是保留/无效地址 集中在少数正常客户端网段
增长速率 几秒内暴涨到几万 随业务量平稳上升
目标端口 全是同一公网服务端口(80/443) 可能是应用端口,伴随大量 ESTAB
内核队列溢出计数 ListenOverflows、ListenDrops 暴涨 增长但不剧烈
业务侧现象 完全无法建立新连接,老连接尚可 变慢、部分连接成功
抓包特征 大量重复 SYN,来源 MAC 相同或异常 SYN 与 ACK 配对正常,只是慢

采集证据的命令:

# 1. 看各状态数量和增速
ss -s
watch -n 1 'ss -s'

# 2. 统计 SYN-RECV 的数量
ss -tan state syn-recv | wc -l

# 3. 按对端 IP 聚合,看来源是否分散
ss -tan state syn-recv | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

# 4. 看内核队列溢出计数
netstat -s | grep -iE 'overflow|dropped|listen'
nstat -az | grep -iE 'ListenOverflows|ListenDrops|TCPBacklogDrop'

# 5. 抓包看 SYN 特征(-n 不做解析,速度快)
tcpdump -i eth0 -n -c 200 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'

如果来源 IP 高度分散、抓包里全是重复 SYN 且没有后续 ACK,基本可以判定是攻击或扫描;如果来源集中、SYN 后面跟着正常 ACK,只是处理慢,那就是负载问题,方向完全不同。

三、急救:先让业务能连上

确认是 SYN Flood 或半连接队列堆积后,第一目标是"恢复服务可用性",然后再谈根治。

手段一:启用 SYN Cookies(最有效、最安全)

SYN Cookies 的原理是:半连接队列满时,服务端不再为每个 SYN 保留状态,而是把连接信息编码进 SYN-ACK 的序列号里,等 ACK 回来再解码重建。这样即使队列满,也不需要占用内存,天然抗 SYN Flood。

# 查看当前状态(1 表示启用)
sysctl net.ipv4.tcp_syncookies

# 临时启用
sysctl -w net.ipv4.tcp_syncookies=1

# 永久启用:写入 /etc/sysctl.d/99-synflush.conf
# net.ipv4.tcp_syncookies = 1
sysctl -p /etc/sysctl.d/99-synflush.conf

现代发行版大多默认就开启了 tcp_syncookies=1,先确认一下,没开就开。这是零风险、高收益的操作。

手段二:调大半连接队列

# 查看当前值
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.core.somaxconn

# 调大(经验值:中小型服务 8192 足够,大流量可到 65535)
# 写入 /etc/sysctl.d/99-synflush.conf
# net.ipv4.tcp_max_syn_backlog = 16384
# net.core.somaxconn = 65535
sysctl -p /etc/sysctl.d/99-synflush.conf

注意:光调 somaxconn 不够,应用层的 backlog 也要跟上(Nginx 是 listen 80 backlog=65535),两者取较小值生效。

手段三:缩短 SYN-RECV 超时

# 减少重试次数,加快回收(默认 5 次,改成 2-3 次可更快释放半连接)
# net.ipv4.tcp_synack_retries = 2
sysctl -w net.ipv4.tcp_synack_retries=2

这个参数会让 SYN-ACK 重试更少,半连接更快被释放,加快队列周转。副作用是弱网客户端可能第一次握手失败,需要重传。经验上设 2 比较稳妥。

四、限速与过滤:用防火墙挡一部分

在内核参数之外,可以用 iptables/nftables 对 SYN 做限速,挡住明显的洪水。

# 限制单个来源 IP 的 SYN 速率(超过则丢弃)
iptables -A INPUT -p tcp --syn -m limit --limit 20/s --limit-burst 40 -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP

# 用 hashlimit 按源 IP 限速(更精细,每个 IP 独立计数)
iptables -A INPUT -p tcp --syn -m hashlimit \
  --hashlimit-name synlimit --hashlimit-above 20/sec --hashlimit-burst 40 \
  --hashlimit-mode srcip --hashlimit-htable-expire 60000 -j DROP

# 丢弃无效状态的新连接
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 保存规则(重启后生效)
# Debian/Ubuntu
apt install -y iptables-persistent
netfilter-persistent save
# RHEL/CentOS/Rocky(有 firewalld 时不要直接和 iptables 混用)
service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables

风险提示:直接 iptables -F 清空会连同你当前 SSH 的连接跟踪一起清掉,可能导致断连;改防火墙前先 iptables-save > /root/iptables.bak 备份,且远程操作时保留一条"放行自己 IP"的规则,最坏情况下能靠它恢复。RHEL 系如果用 firewalld,不要同时手工写 iptables 规则,容易被 firewalld reload 覆盖,应改用 firewall-cmd --add-rich-rule。

# RHEL/CentOS/Rocky 用 firewalld 加限速规则(示例:限制到 80 端口的 SYN 速率)
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" port port="80" protocol="tcp" limit value="50/s" accept'
firewall-cmd --reload
# 回滚:删除该 rich rule
firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" port port="80" protocol="tcp" limit value="50/s" accept'
firewall-cmd --reload

五、判断是否需要上游清洗

本机调优和防火墙限速能应对中小规模攻击,但有物理上限:攻击流量如果超过服务器带宽,或者超过了机器处理 SYN 的能力,任何本机手段都是徒劳的。判断依据:

攻击规模(经验值) 本机手段能否应对 建议动作
< 1 Gbps 通常可以(调优 + 限速) 先自救,观察
1-10 Gbps 勉强,看机器规格 同时联系上游黑洞/清洗
10-100 Gbps 不能 必须走云厂商高防/清洗中心
> 100 Gbps 不能 靠 CDN/Anycast 分散 + 高防

主流云厂商都提供 DDoS 基础防护和可选的流量清洗服务,云服务器遇到大流量攻击时,最有效的做法是联系厂商启用清洗或把流量切到高防 IP。如果业务本身用了 CDN,把源站藏在 CDN 后面,攻击者打的是 CDN 节点而非你的服务器,源站 IP 不暴露能挡掉大量攻击。

六、长期防御思路

短期止血之后,要建立长期防护:

  • 隐藏源站 IP:业务流量走 CDN 或反向代理,服务器只接受来自 CDN 回源 IP 的连接(用安全组/防火墙白名单限制),攻击者找不到真实 IP 就无从下手。
  • 合理设置内核参数:保持 tcp_syncookies=1,把 tcp_max_syn_backlog、somaxconn 调到与实际并发匹配,不要留默认的小值。
  • 连接数监控与告警:对 SYN-RECV 数量、ListenOverflows 设阈值告警(比如 SYN-RECV 超过 5000 持续 1 分钟),把被动救火变成主动发现。
  • 应用层配合:反向代理(Nginx/HAProxy)本身能吸收一部分握手压力;后端只接受代理的连接,进一步缩小暴露面。
  • 定期演练:用压测工具(如 hping3 模拟 SYN、wrk 压正常流量)在测试环境验证参数是否真的生效。
# 监控脚本示例:SYN-RECV 超过阈值就记录
#!/bin/bash
th=5000
n=$(ss -tan state syn-recv | wc -l)
if [ "$n" -gt "$th" ]; then
  echo "$(date) SYN-RECV=$n 超过阈值" >> /var/log/synmon.log
  ss -tan state syn-recv | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head >> /var/log/synmon.log
fi

常见问题

Q:ss 里 SYN-RECV 数量很少,但业务还是连不上,可能是什么原因?
不一定是队列问题。检查是不是全连接队列满(LISTEN 行 Recv-Q 接近 Send-Q),或者应用进程卡死、端口被防火墙拦。SYN-RECV 多只是"半连接堆积"这一种可能。

Q:开了 tcp_syncookies=1 会不会有副作用?
基本没有明显副作用。极端情况下会损失一些 TCP 高级选项(如窗口缩放相关的协商可能受限),但在遭受攻击时它是救命的。日常也建议一直开着。

Q:把 tcp_max_syn_backlog 调到 65535 就好了吗?
不够。它只管半连接队列,还要同步调 net.core.somaxconn 和应用 backlog(Nginx listen ... backlog=),并且 tcp_max_syn_backlog 的效果受 somaxconn 影响。三者要配套。

Q:怎么判断攻击已经超出本机处理能力?
看服务器网卡入口速率(sar -n DEV 1 或 ip -s link 的 RX 速率)和 CPU 软中断占用。如果入口带宽接近网卡上限、软中断把 CPU 打满,说明流量已经压到物理极限,必须上游清洗。

Q:封禁来源 IP 有用吗?
对少量固定来源的扫描有用,对伪造源 IP 的 SYN Flood 基本没用(你封的是假地址),还可能误伤正常用户。更推荐用限速(hashlimit)而非逐个封 IP。

总结

SYN-RECV 是"发了 SYN-ACK 等不到 ACK"的中间状态,正常应极短暂;大量堆积通常是半连接队列被占满,可能是 SYN Flood,也可能是客户端不回应或服务端处理慢。

判断方向看证据:来源 IP 分散、增长暴涨、ListenDrops 激增、抓包全是无后续的 SYN,才更像攻击;来源集中、有正常 ACK 则偏负载问题。

急救顺序是:确认并启用 tcp_syncookies=1(零风险高收益)→ 调大 tcp_max_syn_backlog 和 somaxconn → 调小 tcp_synack_retries 加快回收 → 用 iptables hashlimit / firewalld rich rule 做 SYN 限速。

攻击规模超过本机带宽或处理能力时,本机手段无效,要联系上游清洗或启用高防;长期防御靠隐藏源站(CDN + 回源白名单)、参数常备、连接数告警和应用层代理。

所有防火墙和内核参数修改前先备份(iptables-save、sysctl -a)、留好带外通道、准备回滚;防火墙临时规则可用 firewall-cmd --add-rich-rule ... --timeout=60 先测试。