1)背景:前端入口链路
我们的站点前端是典型的两层入口:
- HAProxy:公网入口(80/443),负责分流与转发
- Nginx:承载前端静态资源与后端反代
部署架构图

2)问题现象:同样是 SlowHTTP,HTTPS “十几秒断光”,HTTP 却能一直挂着
我们用 slowhttptest 测试 Slow Headers / Slowloris(慢慢发送请求头,让服务器长时间保持连接,最终耗尽连接资源)时:(HAProxy Technologies)
- HTTPS(443):连接在 10 多秒左右大量断开,攻击很难长期占用资源
- HTTP(80):连接能长期保持;攻击持续后才出现 429,但 429 会影响所有正常用户访问
原因 : 一开始我们把防护都写在 Nginx(例如 header 超时等),结果出现非常反直觉的情况:HTTPS 有效,HTTP 无效。
3)根因分析:HTTP/HTTPS 的“第一层解析 HTTP 的位置”不同
这次的关键点不是 Nginx 配得对不对,而是:
Slowloris 卡住的是“第一层负责接收/解析 HTTP 请求头”的组件。
✅ HTTPS(443)为什么能防住
我的 443 链路是:
- HAProxy:
mode tcp + SNI 分流(不解析 HTTP)
- 后端:
send-proxy-v2 透传到 Nginx
- Nginx:负责 TLS 终止与 HTTP 解析(第一层真正“读请求头”的地方)
因此 Slowloris 的“慢请求头”最终会到 Nginx,Nginx 的相关超时策略会把它掐掉,所以你看到 HTTPS 连接会在十几秒内“断光”。
另外,Nginx 要接收 PROXY 协议,必须在 listen 上启用 proxy_protocol。(loadbalancer.org)
❌ HTTP(80)为什么最初防不住
我的 80 链路是:
- HAProxy:
mode http(会解析 HTTP)
这意味着 HAProxy 才是第一层接收并解析 HTTP 请求头的地方。而 HAProxy 在收到完整请求之前不会把请求转发到后端(官方说明:只有收到完整请求才会发给后端,因此后端往往看不到 slowloris 流量)。(HAProxy Technologies)
所以你在 Nginx 上做的“读请求头超时”对 HTTP 链路会出现一个现实问题:
慢头连接根本没到 Nginx,就卡在 HAProxy 这一层了。
这就是“HTTPS 能防、HTTP 防不住”的根本原因:两条链路的防护点不在同一层。
4)最终解决方案:把 SlowHTTP 防护前移到 HAProxy 的 80(HTTP)入口
要在 HAProxy mode http 下解决 slowloris,核心就一条:
在此基础上我们叠加 stick-table,用“按源 IP 的并发连接数 / 请求速率”做更精细的保护与兜底。(HAProxy Technologies)
5)最终版配置
下面给出最终版(精简但完整可用)的 HAProxy + Nginx 关键配置结构,你可以把业务路由部分按自己的域名/SNI/后端继续扩展。
A. HAProxy(最终版)
版本:haproxy:2.3(Docker)
说明:443 维持 TCP/SNI 分流,80 负责 HTTP 分流 + SlowHTTP 防护
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| global log 127.0.0.1 local0 daemon maxconn 2000
defaults log global option redispatch retries 3 timeout connect 5s timeout client 50s timeout server 50s
# 管理页面(可选) frontend admin_stats bind :8070 mode http stats enable stats uri /admin stats refresh 30s stats auth admin:xxxxxxxx stats hide-version
##################################################################### # HTTP :80 入口(关键:在这里防 slowloris / slowhttp) ##################################################################### frontend header_front bind *:80 mode http option httplog option forwardfor
# --- 核心 1:专杀 Slow Headers / Slowloris --- # 限制接收完整请求头时间:慢头无法“续命” timeout http-request 10s
# 可选:缩短 keep-alive 空等时间,减少连接长期占用 timeout http-keep-alive 10s
# 可选:收紧 client(视业务而定) timeout client 30s
# --- 核心 2:stick-table(按IP并发/速率兜底)--- stick-table type ip size 200k expire 30s store conn_cur,conn_rate(10s),http_req_rate(10s)
# 连接一建立就跟踪:对“慢头阶段”的连接非常关键 tcp-request connection track-sc0 src acl abuse_conn sc0_conn_cur gt 80 tcp-request connection reject if abuse_conn
# 请求层限速(按业务调整阈值) http-request track-sc0 src acl abuse_rps sc0_http_req_rate gt 100 http-request deny deny_status 429 if abuse_rps
# --- 你的业务分流规则(示例保留你的结构)--- acl finereport_h5 hdr_dom(host) -i finereport.xxxxx.com.cn http-request redirect scheme https if finereport_h5
acl iphone_browser hdr_reg(User-Agent) -i iPhone acl ipad_browser hdr_reg(User-Agent) -i iPad acl android_browser hdr_reg(User-Agent) -i Android use_backend app_backend if iphone_browser or ipad_browser or android_browser default_backend pc_backend
backend app_backend mode http option httpchk server mom 192.168.139.118:30001 check # 注意:不要再手动 set-header X-Forwarded-For,否则会和 option forwardfor 叠加导致 "ip, ip" http-request set-header X-Real-IP %[src]
backend pc_backend mode http server mom-app 192.168.139.118:30002 http-request set-header X-Real-IP %[src]
##################################################################### # HTTPS :443 入口(维持 TCP + SNI 分流,交给 Nginx 终止 TLS) ##################################################################### frontend https_frontend bind *:443 mode tcp
tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 }
acl app_host req.ssl_sni -i app.xxxxx.com.cn acl finereport_host req.ssl_sni -i finereport.xxxx.com.cn
use_backend app_https_backend if app_host use_backend finereport_https_backend if finereport_host default_backend pc_https_backend
backend pc_https_backend mode tcp server mom-https 192.168.139.118:18443 send-proxy-v2
backend app_https_backend mode tcp server app-https 192.168.139.118:18443 send-proxy-v2
backend finereport_https_backend mode tcp server fr-https 192.168.139.118:18443 send-proxy-v2
|
关键点回顾:
B. Nginx(最终版:只保留与链路相关的两句)
你不需要在这篇文章里展开 Nginx 的各类参数(因为这次问题本质在 80 的 HAProxy)。但为了说明链路,保留两句就够:
1 2 3 4 5
| listen 80;
listen 443 ssl proxy_protocol;
|
proxy_protocol 的意义:让 Nginx 能从 HAProxy 透传的 PROXY 协议里获取真实源地址。(loadbalancer.org)
6)验证方式:怎么确认 HTTP 真的“像 HTTPS 一样”防住了
- 再跑一次 slowhttptest(SLOW HEADERS 模式)
- 观察 80 端口:连接会在
timeout http-request 10s 附近开始大量断开,不会长期“挂住”
- 443 端口:保持原有效果(十几秒内大量断开)
- HAProxy 日志/统计
- 你会看到更多连接在请求未完成阶段被拒绝/超时
- 若开了 stats(:8070),也能看到前端并发回落更快
7)总结:这次踩坑的真正经验
- 同样是 slowloris,HTTP/HTTPS 的防护点可能完全不同。
- 当 80 端口 HAProxy 是
mode http 时,慢头卡在 HAProxy,Nginx 的“读头超时”自然不生效。
- 解决它的正确方式是:在 HAProxy 80 入口加
timeout http-request + stick-table,把 slowhttp “掐死在入口层”。