适用场景
HTTP/2 已被主流站点普遍启用,但多数配置只关注「开启」而忽略「限制」。2023 年披露的 Rapid Reset(CVE-2023-44487)证明,攻击者只需在单条 TCP 连接上高频发起并立即重置流,即可绕过传统「单 IP 连接数」限制,用极低带宽打满服务器内存与 CPU。本文面向已启用 HTTP/2 的 Nginx 站点,给出并发流限制、连接复用上限与超时兜底的完整加固配置,适用于尚在用小版本 Nginx、或尚未对 HTTP/2 做任何限流的运维场景。
前置条件
- Nginx 版本 ≥ 1.25.3(该版本起内置 Rapid Reset 缓解),或 1.24.0/1.22.1 的修复分支
- 已配置 TLS 证书,站点通过 443 端口提供 HTTPS 服务
- 具备
nginx -t与systemctl reload nginx权限 - 如前端有 CDN,需确认 CDN 到源站的协议版本,避免限流参数错配
原理说明
HTTP/2 的多路复用允许在一条 TCP 连接上并行处理多个请求流。默认配置下 Nginx 通过 http2_max_concurrent_streams 限制单连接的最大并发流数,但对「已建立又立刻 RST_STREAM 的流」计数并不严格。Rapid Reset 攻击正是利用这一间隙:客户端发出 HEADERS 立即发送 RST_STREAM,服务端已经分配了流处理资源但请求被取消,于是一条连接可在一秒内制造数千次「创建—销毁」循环。
有效的加固由三层构成:
- 流层:限制单连接并发流数,并在连接建立初期使用更低的并发上限
- 连接层:限制单连接可处理的请求总数(keepalive_requests),强制攻击者反复建连;反复建连会被连接层限流与四层防护捕获
- 超时层:缩短空闲与头部读取超时,让僵尸连接快速释放资源
操作步骤
1. 确认版本与当前 HTTP/2 状态
nginx -v
# 输出示例:nginx version: nginx/1.25.4
nginx -T 2>/dev/null | grep -i http2
若版本低于 1.25.3 且无法升级,需在 Nginx 前置 CDN 或 WAF 层开启 HTTP/2 并关闭源站 HTTP/2。
2. 配置 HTTP/2 并发流与连接复用限制
在 http 块中声明全局参数,或在特定 server / location 中覆盖:
# /etc/nginx/nginx.conf —— http 块
http {
# 单连接最大并发流数:默认 128,抗 Rapid Reset 建议下调
http2_max_concurrent_streams 64;
# 单连接最多处理的请求数:达到后服务端发送 GOAWAY,强制重新建连
keepalive_requests 200;
# 连接空闲超时:缩短可加快资源回收
keepalive_timeout 15s;
# 请求头读取超时:阻断慢速头部攻击
client_header_timeout 10s;
client_body_timeout 15s;
# 单 IP 并发连接与请求速率限制(连接层兜底)
limit_conn_zone $binary_remote_addr zone=perip_conn:10m;
limit_req_zone $binary_remote_addr zone=perip_req:10m rate=30r/s;
include /etc/nginx/conf.d/*.conf;
}
3. 在 443 server 块中启用并应用限制
server {
listen 443 ssl;
server_name www.example.com;
# Nginx 1.25.1+ 使用独立指令开启 HTTP/2
http2 on;
ssl_certificate /etc/nginx/ssl/example.crt;
ssl_certificate_key /etc/nginx/ssl/example.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 应用连接与请求速率限制
limit_conn perip_conn 50;
limit_req zone=perip_req burst=60 nodelay;
# 限制单 IP 同时打开的 HTTP/2 连接数(连接层)
limit_conn_status 429;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
参数要点:http2_max_concurrent_streams 64 表示每条连接最多 64 个并行流;keepalive_requests 200 让一条连接处理 200 个请求后即关闭,攻击者若想维持高速率只能不断新建连接,从而撞上 limit_conn;client_header_timeout 10s 阻断慢速发头部的变种攻击。
4. 显式关闭明文 h2c
h2c(HTTP/2 over cleartext)缺乏 TLS 保护且易被用于请求走私,除内部可信链路外应一律关闭。检查并确保 80 端口仅做 301 跳转,不配置 http2:
server {
listen 80;
server_name www.example.com;
return 301 https://$host$request_uri;
}
5. 应用配置
sudo nginx -t && sudo systemctl reload nginx
配置验证
确认 HTTP/2 已启用且限流生效:
# 检查服务端协商的协议
curl -sI --http2 https://www.example.com | head -1
# 预期:HTTP/2 200
# 通过 ALPN 确认 TLS 层已协商 h2
openssl s_client -connect www.example.com:443 -alpn h2 -servername www.example.com </dev/null 2>/dev/null | grep -i "ALPN protocol"
# 预期:ALPN protocol: h2
验证并发流与请求速率限制是否触发:
# 单 IP 快速发起大量请求,观察是否返回 429
ab -n 500 -c 80 -k https://www.example.com/ 2>&1 | grep -E "Non-2xx|Complete requests"
# 预期:出现 Non-2xx responses(429),未全部 200 即说明限流生效
查看错误日志确认触发记录:
tail -f /var/log/nginx/error.log | grep -iE "limiting connections|limiting requests"
常见问题
FAQ 1:开源版 Nginx 不显示 http2_max_concurrent_streams 参数,怎么确认生效?
该指令在 Nginx 1.25.1 之后改为由 http2 指令与新的 HTTP/2 模块管理,部分版本的 nginx -T 不会回显。确认方式为运行时观察:使用 h2load(nghttp2 客户端)指定并发流数压测,观察连接是否在达到阈值后被拒绝新流:
h2load -n 1000 -c 10 -m 128 https://www.example.com/
# 若并发流上限为 64,输出中的 max concurrent streams 应显示 64
若版本确实不支持该指令,应优先升级 Nginx,或在 CDN 层统一限制并发流。
FAQ 2:加了 limit_conn 之后,正常用户偶尔被 429,如何平衡?
429 误伤通常源于阈值过紧或 CDN 回源场景。处理原则:
- 区分真实 IP:站点前置 CDN 时,
$binary_remote_addr取到的是 CDN 节点 IP,会误封整个 CDN。需启用 real_ip 模块(set_real_ip_from + real_ip_header X-Forwarded-For)还原客户端 IP - 区分资源类型:静态资源与 API 的合理并发差异大,可对
/api/单独设置更严的 limit_req,对静态目录放宽 - 预留突发:limit_req 使用 burst 参数吸收正常突发,nodelay 避免排队延迟;阈值建议从日均 P99 并发上浮 50% 起步,再按真实告警逐步收紧
调整后务必回归验证一次登录、下单等关键链路,确认无 429 后再全量放行。
总结
HTTP/2 的安全问题不在协议本身,而在「开启后未限制」。Rapid Reset 一类攻击绕过的是单 IP 连接数限制,因此加固重点应放在并发流上限(http2_max_concurrent_streams)、单连接请求数上限(keepalive_requests)与连接数/速率双重限流三层。落地顺序:先确认 Nginx 版本已含 Rapid Reset 缓解,再按业务量设定并发流与请求阈值,最后用 h2load 与 ab 压测验证限流确实触发,并核查 CDN 场景下真实 IP 是否已还原。