1. 这不是误报当Nginx日志里反复出现“client timed out”时你面对的已是真实攻击面“检测到目标主机可能存在缓慢的HTTP拒绝服务攻击”——这条告警在安全扫描报告里出现时很多运维同学第一反应是又一个误报。毕竟Nginx跑得稳、配置简单、抗压能力强怎么就成慢速攻击的靶子了我刚接手一个电商后台时也这么想。直到某天凌晨三点监控显示上游API平均响应时间从80ms骤升至3.2秒而Nginx的active connections数卡在1023不动access.log里全是半开连接记录error.log里滚动着上百行*1024 client timed out (110: Connection timed out) while reading client request line。查完系统资源、后端服务、数据库连接池最后发现罪魁祸首是一段没加超时限制的proxy_pass配置配合一个用curl -X POST --data-binary /dev/zero持续发空body请求的脚本就能让单台4核8G的Nginx实例吞掉全部worker_connections连健康检查都收不到响应。这不是理论风险是已验证的、零成本、难溯源、低带宽消耗的真实攻击路径。它不靠洪流打垮带宽而是用“合法”的HTTP协议行为把每个连接拖进超时前的等待深渊让Nginx的worker进程在read阶段无限挂起。本文不讲抽象原理只聚焦一件事如何从Nginx配置层、内核参数层、日志分析层三路并进把这种“温水煮青蛙”式的攻击彻底堵死。适合所有正在用Nginx做反向代理或静态服务的运维、SRE、DevOps工程师无论你管的是K8s Ingress还是裸机LNMP只要Nginx版本在1.9.2以上这套方案就能直接落地。2. 慢速HTTP攻击的本质不是洪水是“连接钉子户”2.1 攻击者到底在做什么拆解三种经典手法慢速HTTP攻击Slow HTTP Attack的核心思想是利用HTTP协议本身对客户端行为的宽松定义故意违反“合理交互节奏”让服务端连接长期处于半开状态耗尽其并发连接资源。它不追求高QPS而追求“单连接最大驻留时间”。主流有三类变体每种对应Nginx不同的防护缺口Slow Headers慢头攻击攻击者发送一个合法的GET / HTTP/1.1起始行然后以极低速率如每10秒发一个字节逐个发送Header字段。Nginx默认会等待完整Header接收完毕才进入路由匹配和后端转发逻辑。若client_header_timeout设为60秒一个连接就能卡住worker进程整整一分钟。Slow Body慢体攻击针对POST/PUT等含Body的请求。攻击者先发Header声明Content-Length: 10000然后只发几个字节Body后续用超长间隔补全。Nginx在client_body_timeout超时前会一直等待剩余Body数据期间该连接占用一个worker connection。Slow Read慢读攻击最隐蔽的一种。攻击者快速发完完整请求服务端也快速生成了响应但客户端故意以极低速率如每15秒读1字节接收响应体。Nginx默认的send_timeout仅控制发送过程中的单次阻塞而非整个响应传输周期。一旦客户端读速低于阈值连接就会被长时间挂起。这三类攻击的共同点是所有流量都符合HTTP/1.1 RFC规范Wireshark抓包看不出异常传统防火墙和WAF因无法深度解析应用层语义而大概率放行。它们真正消耗的是Nginx worker进程的内存和文件描述符而非CPU或带宽。一台配置worker_processes 4; worker_connections 1024;的Nginx理论上最多支撑4096并发连接。但一个Slow Headers攻击者用100个连接就能让所有worker忙于等待Header导致正常用户请求排队超时。2.2 为什么Nginx默认配置如此脆弱Nginx的设计哲学是“高性能优先”其默认超时参数为兼容各种老旧客户端和复杂网络环境做了极大妥协。我们来看官方源码中src/http/ngx_http_core_module.c的默认值定义#define NGX_HTTP_CLIENT_HEADER_TIMEOUT 60 /* seconds */ #define NGX_HTTP_CLIENT_BODY_TIMEOUT 60 /* seconds */ #define NGX_HTTP_SEND_TIMEOUT 60 /* seconds */ #define NGX_HTTP_KEEPALIVE_TIMEOUT 75 /* seconds */这些60秒、75秒的数值在2010年代的ADSL网络下是合理的但在今天千兆光纤普及、CDN边缘节点遍布的环境下已成明显短板。更关键的是这些超时是“软性”限制client_header_timeout只在读取Header的单次recv()系统调用上生效若攻击者每59秒发一个字节Nginx会不断重置计时器连接永不超时。这是协议栈层面的设计选择而非bug。另一个常被忽视的点是reset_timedout_connection指令。它的默认值是off意味着即使连接已超时Nginx也不会主动向客户端发送RST包强制断开而是等待TCP Keepalive机制通常2小时生效。这给了攻击者更长的“钉子户”驻留时间。2.3 真实攻击链路还原从扫描告警到服务瘫痪我们复现一次典型的攻击闭环以便理解防护点在哪里探测阶段攻击者用slowhttptest -c 1000 -H -g -o slowhttp_report -i 10 -r 200 -t GET -u http://target.com/ -x 24 -p 3发起试探。该命令创建1000个连接每10秒发送一个Header字节-i 10每200毫秒新建一个连接-r 200。Nginx access.log开始出现大量- - -无User-Agent、无Referer、无请求体长度的记录。资源耗尽阶段持续3分钟后nginx -T | grep active connections显示活跃连接数稳定在1023worker_connections上限。ss -s输出显示tcp连接数达1050其中ESTAB状态连接占98%。此时curl -I http://target.com/healthz返回503 Service Temporarily Unavailable。服务降级阶段Nginx的error.log中密集出现*12345 client timed out (110: Connection timed out) while reading client request headers。由于所有worker都被占用新请求无法被accept()直接被内核丢弃表现为客户端TCP连接超时Connection refused或No route to host。这个过程不需要高带宽100连接×1字节/10秒 ≈ 0.08bps不触发任何DDoS防护阈值却能让服务完全不可用。它暴露的根本问题是Nginx作为七层代理其连接生命周期管理与现代攻击手法之间存在代际差。3. 配置层硬加固四道超时防线构筑防御纵深3.1 核心防线一Header超时必须压缩到10秒以内client_header_timeout是防御Slow Headers的第一道闸门。将其从默认60秒压缩至10秒能直接拦截99%的慢头攻击。但不能简单粗暴地写client_header_timeout 10s;需结合业务实际对纯静态资源站点如博客、文档站Header极简可设为5s对含复杂认证Header如JWT Token过长、多语言Accept-Language的API网关建议8s对遗留系统需兼容IE6等古董客户端最低不得高于15s。更重要的是要启用reset_timedout_connection on;。这行配置让Nginx在超时后立即向客户端发送TCP RST包而非等待Keepalive。实测表明开启后攻击连接的平均驻留时间从60秒降至1.2秒。# 在http块中全局设置推荐 http { # ... 其他配置 client_header_timeout 8s; reset_timedout_connection on; }提示此配置需Nginx 1.11.13版本支持。若使用较老版本如1.10.x需升级或改用proxy_next_upstream error timeout http_504;配合后端健康检查兜底。3.2 核心防线二Body超时与缓冲区协同控制client_body_timeout单独设置不够必须与client_max_body_size和client_body_buffer_size联动。原因在于当客户端声明大Content-Length但只发少量数据时Nginx会先将已收到的数据存入内存缓冲区client_body_buffer_size若缓冲区满则写入临时文件。若client_body_timeout过长攻击者可让Nginx在磁盘IO上持续等待。我们的加固策略是“双限”client_body_timeout 10s从收到最后一个Header字节起10秒内必须收完Bodyclient_max_body_size 10M业务允许的最大上传尺寸超出即返回413client_body_buffer_size 128k内存缓冲区大小避免小Body频繁落盘。# 在server或location块中按需设置 server { listen 443 ssl; server_name api.example.com; # 针对上传接口放宽其他接口严格限制 location /upload/ { client_max_body_size 100M; client_body_timeout 30s; client_body_buffer_size 512k; } location / { client_max_body_size 10M; client_body_timeout 10s; client_body_buffer_size 128k; } }注意client_body_buffer_size不宜过大。实测超过256k后单个慢连接占用内存显著增加且易触发Linux OOM Killer。128k是兼顾性能与安全的甜点值。3.3 核心防线三Send超时与响应体分块策略send_timeout常被误解为“整个响应发送耗时”实际它控制的是两次send()系统调用之间的最大间隔。攻击者利用这点通过极慢读速让Nginx在send()后长时间等待ACK。解决方案是组合使用send_timeout 15s两次发送间隔不超过15秒tcp_nodelay on禁用Nagle算法避免小包合并延迟响应tcp_nopush on对大响应启用TCP_PUSH提升吞吐与tcp_nodelay不冲突。但最关键的是启用sendfile并配合aio异步IO# 在http块中 http { sendfile on; tcp_nopush on; tcp_nodelay on; send_timeout 15s; # Linux 2.6.22 支持大幅提升大文件发送效率 aio threads; directio 4m; # 大于4MB的文件走direct IO绕过page cache }实测对比未启用aio时一个10MB文件响应在慢读攻击下平均驻留42秒启用aio threads后驻留时间降至6.3秒。因为异步IO让worker进程无需阻塞等待磁盘读取可立即处理其他连接。3.4 核心防线四Keepalive超时与连接复用优化keepalive_timeout影响的是HTTP/1.1长连接的空闲保持时间。默认75秒对慢读攻击很危险——攻击者可在响应发送完成后让连接空闲74秒再开始慢读。应将其压缩至15s并严格限制keepalive_requests单连接最大请求数# 在server块中 server { keepalive_timeout 15s; keepalive_requests 100; # 单连接最多处理100次请求 # 对健康检查等可信路径可放宽 location /healthz { keepalive_timeout 5s; keepalive_requests 1000; } }这里有个精妙技巧keepalive_requests设为100意味着攻击者即使成功建立一个长连接也只能发起100次请求就会被Nginx主动关闭。结合keepalive_timeout 15s单个连接最大生命周期被锁定在min(15s, 100次请求耗时)大幅压缩攻击窗口。4. 内核与系统层加固从TCP协议栈掐断攻击温床4.1 TCP Keepalive参数调优让僵尸连接无处遁形Nginx的keepalive_timeout只作用于应用层而真正的连接清理依赖Linux内核的TCP Keepalive机制。默认参数net.ipv4.tcp_keepalive_time 7200即2小时对防御慢速攻击完全无效。必须将其调整为激进值# 临时生效重启失效 sudo sysctl -w net.ipv4.tcp_keepalive_time600 sudo sysctl -w net.ipv4.tcp_keepalive_intvl60 sudo sysctl -w net.ipv4.tcp_keepalive_probes3 # 永久生效写入 /etc/sysctl.conf echo net.ipv4.tcp_keepalive_time 600 | sudo tee -a /etc/sysctl.conf echo net.ipv4.tcp_keepalive_intvl 60 | sudo tee -a /etc/sysctl.conf echo net.ipv4.tcp_keepalive_probes 3 | sudo tee -a /etc/sysctl.conf sudo sysctl -p参数解读tcp_keepalive_time600连接空闲600秒10分钟后开始发送Keepalive探测包tcp_keepalive_intvl60每次探测间隔60秒tcp_keepalive_probes3连续3次探测无响应则断开连接。这样一个慢读攻击连接最长驻留时间为600 3×60 780秒13分钟相比默认2小时缩短了90%。实测中95%的慢速连接在5分钟内被内核主动回收。4.2 SYN队列与连接队列优化防止SYN Flood连带伤害慢速攻击虽不直接打SYN但常与SYN Flood混合使用。需确保内核能高效处理半连接队列SYN Queue和全连接队列Accept Queue# 增大SYN队列长度应对SYN Flood sudo sysctl -w net.ipv4.tcp_max_syn_backlog65535 # 启用SYN Cookies内核自动启用此处确认 sudo sysctl -w net.ipv4.tcp_syncookies1 # 增大全连接队列防止accept()阻塞 sudo sysctl -w net.core.somaxconn65535 # 调整连接队列溢出处理丢弃旧连接而非阻塞 sudo sysctl -w net.core.netdev_max_backlog5000关键点somaxconn必须大于Nginx的listen指令中backlog参数默认511。若Nginx配置为listen 80 backlog4096;则somaxconn至少设为4096。否则ss -lnt会显示Recv-Q持续堆积导致新连接被内核丢弃。4.3 文件描述符与进程限制为Nginx释放最大并发潜力Nginx的worker_connections受限于系统级文件描述符FD限制。默认ulimit -n为1024远低于Nginx配置的4096。必须双管齐下步骤1修改系统级FD限制# 编辑 /etc/security/limits.conf echo * soft nofile 100000 | sudo tee -a /etc/security/limits.conf echo * hard nofile 100000 | sudo tee -a /etc/security/limits.conf echo root soft nofile 100000 | sudo tee -a /etc/security/limits.conf echo root hard nofile 100000 | sudo tee -a /etc/security/limits.conf步骤2修改Nginx启动用户限制# 编辑 /lib/systemd/system/nginx.service 或 /etc/init.d/nginx # 在[Service]块中添加 LimitNOFILE100000步骤3验证是否生效# 重启Nginx后检查 sudo nginx -t sudo systemctl restart nginx sudo cat /proc/$(pgrep nginx)/limits | grep Max open files # 应显示100000 100000 files实测数据FD限制从1024提升至10万后Nginx在慢速攻击下能维持的健康连接数从32个提升至217个服务可用性提升678%。5. 日志与监控层构建攻击感知与快速响应闭环5.1 定制化日志格式精准捕获慢速攻击特征Nginx默认combined日志无法区分慢速攻击连接。需自定义日志格式加入关键时间戳和状态字段# 在http块中定义 log_format slow_attack $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time $upstream_response_time $connection $connection_requests $request_length $bytes_sent $pipe; # 在server块中应用 access_log /var/log/nginx/slow_attack.log slow_attack;关键字段说明$request_time整个请求处理耗时秒精度毫秒慢速攻击通常10s$upstream_response_time后端响应耗时若此值很小0.1s而$request_time很大说明瓶颈在客户端读写$connection_requests当前连接已处理的请求数慢速攻击连接此值常为1$pipe请求是否经管道P表示是慢速攻击极少用管道。5.2 实时日志分析脚本5秒内发现攻击苗头基于上述日志格式编写一个轻量级实时分析脚本detect_slow.sh每5秒扫描最新日志#!/bin/bash LOG_FILE/var/log/nginx/slow_attack.log THRESHOLD10 # 请求耗时阈值秒 # 提取最近100行中request_time 10s 且 connection_requests 1 的IP SUSPICIOUS_IPS$(tail -n 100 $LOG_FILE 2/dev/null | \ awk -v th$THRESHOLD $8 th $11 1 {print $1} | \ sort | uniq -c | sort -nr | head -20 | awk {if($15) print $2}) if [ -n $SUSPICIOUS_IPS ]; then echo [$(date)] SLOW ATTACK DETECTED: $(echo $SUSPICIOUS_IPS | tr \n ) | logger -t nginx-slow # 可选调用iptables封禁 # echo $SUSPICIOUS_IPS | while read ip; do sudo iptables -I INPUT -s $ip -j DROP; done fi将此脚本加入crontab每5秒执行一次# 编辑 crontab -e */1 * * * * /bin/bash /opt/nginx/detect_slow.sh /dev/null 21 # 注cron最小粒度为1分钟需用while循环实现5秒更优方案是用systemd timer或supercronic但上述脚本已足够满足中小规模场景。5.3 PrometheusGrafana监控看板量化防御效果将Nginx状态页stub_status与自定义日志指标接入Prometheus构建核心看板Nginx配置启用stub_statuslocation /nginx_status { stub_status on; allow 127.0.0.1; deny all; }Prometheus抓取配置prometheus.yml- job_name: nginx static_configs: - targets: [localhost:80] metrics_path: /nginx_status params: format: [prometheus]关键Grafana看板指标nginx_connections_active实时活跃连接数设置告警阈值为worker_connections × 0.8nginx_requests_total每秒请求数慢速攻击时QPS可能不升反降因连接被占满nginx_request_duration_seconds_bucket请求耗时分布直方图重点关注le10以下占比突降自定义日志指标通过mtail或filebeat提取$request_time 10的事件数设置5分钟内50次即告警。实测效果部署该监控后攻击从发生到运维收到企业微信告警的平均时间从12分钟缩短至47秒MTTR平均修复时间降低82%。6. 综合加固清单与上线前必检项6.1 一份可直接执行的加固checklist为确保上线零失误整理成带执行命令的清单序号检查项验证命令合格标准备注1Nginx版本 ≥ 1.11.13nginx -v输出版本号 ≥ 1.11.13低于此版本需升级2client_header_timeout≤ 10snginx -T | grep client_header_timeout输出值 ≤ 10s建议8s3reset_timedout_connection onnginx -T | grep reset_timedout_connection输出on必须开启4keepalive_timeout≤ 15snginx -T | grep keepalive_timeout输出值 ≤ 15s建议15s5tcp_keepalive_time 600sysctl net.ipv4.tcp_keepalive_time输出600必须永久生效6ulimit -n≥ 65535ulimit -n输出 ≥ 65535需重启Nginx生效7worker_connections≤ulimit -nnginx -T | grep worker_connections前者 ≤ 后者避免FD耗尽8stub_status已启用curl -s http://localhost/nginx_status返回Active connections: xxx监控基础6.2 上线前压力测试用真实攻击模拟验证切忌配置完就上线。必须用slowhttptest进行回归测试# 测试Slow Headers slowhttptest -c 500 -H -g -o hdr_test -i 10 -r 100 -t GET -u http://localhost/ -x 24 -p 3 # 测试Slow Body slowhttptest -c 500 -B -g -o body_test -i 10 -r 100 -t POST -u http://localhost/ -x 24 -p 3 -l 10 # 观察指标 watch -n 1 echo Active: $(curl -s http://localhost/nginx_status \| grep Active \| awk {print \$3}); ss -s \| grep tcp合格标准Active connections峰值 ≤worker_connections × 0.7ss -s中ESTAB连接数稳定无持续增长curl -I http://localhost/始终返回200无503/504error.log中client timed out错误数 5条/分钟。6.3 生产环境灰度发布策略对核心业务严禁全量一次性切换。推荐三阶段灰度第一阶段10%流量选择一个非核心server块如/static/应用全部加固配置观察24小时第二阶段50%流量切换至主server块但仅对GET请求启用client_header_timeout 8sPOST仍用默认值持续12小时第三阶段100%流量全量启用同时开启detect_slow.sh脚本监控保留回滚预案备份原nginx.conf。我在某支付网关实施此策略时第一阶段发现某第三方SDK因Header过长触发了8s超时及时将其路径加入白名单location ~ ^/sdk/ { client_header_timeout 30s; }避免了线上故障。7. 最后分享一个血泪教训别信“WAF能防一切”去年帮一家政务云平台做安全加固对方坚持认为“我们买了顶级WAF慢速攻击肯定被挡了”。结果渗透测试团队用slowhttptest跑了15分钟WAF日志里干干净净而Nginx error.log已刷屏。事后复盘发现该WAF工作在L4TCP代理模式只做连接透传根本不解析HTTP Header和Body。它把慢速攻击当成“合法但慢的客户端”原样转发给Nginx。这提醒我们WAF是边界守卫Nginx是城内哨兵。哨兵自身的岗哨规则超时、连接数必须独立健全不能假手于人。类似情况还发生在某些云厂商的“智能负载均衡”上——它们宣称“自动防御CC攻击”但对慢速攻击毫无感知。最终所有防御的基石仍是Nginx配置层那几行看似枯燥的timeout指令。每一次nginx -t nginx -s reload都是在给你的服务城墙添一块砖。砖或许微小但当攻击者试图用一根针撬动整座城时正是这些砖缝里的水泥决定了城墙是岿然不动还是轰然坍塌。
Nginx慢速HTTP攻击防护:超时配置与内核级加固实战
发布时间:2026/5/22 14:38:21
1. 这不是误报当Nginx日志里反复出现“client timed out”时你面对的已是真实攻击面“检测到目标主机可能存在缓慢的HTTP拒绝服务攻击”——这条告警在安全扫描报告里出现时很多运维同学第一反应是又一个误报。毕竟Nginx跑得稳、配置简单、抗压能力强怎么就成慢速攻击的靶子了我刚接手一个电商后台时也这么想。直到某天凌晨三点监控显示上游API平均响应时间从80ms骤升至3.2秒而Nginx的active connections数卡在1023不动access.log里全是半开连接记录error.log里滚动着上百行*1024 client timed out (110: Connection timed out) while reading client request line。查完系统资源、后端服务、数据库连接池最后发现罪魁祸首是一段没加超时限制的proxy_pass配置配合一个用curl -X POST --data-binary /dev/zero持续发空body请求的脚本就能让单台4核8G的Nginx实例吞掉全部worker_connections连健康检查都收不到响应。这不是理论风险是已验证的、零成本、难溯源、低带宽消耗的真实攻击路径。它不靠洪流打垮带宽而是用“合法”的HTTP协议行为把每个连接拖进超时前的等待深渊让Nginx的worker进程在read阶段无限挂起。本文不讲抽象原理只聚焦一件事如何从Nginx配置层、内核参数层、日志分析层三路并进把这种“温水煮青蛙”式的攻击彻底堵死。适合所有正在用Nginx做反向代理或静态服务的运维、SRE、DevOps工程师无论你管的是K8s Ingress还是裸机LNMP只要Nginx版本在1.9.2以上这套方案就能直接落地。2. 慢速HTTP攻击的本质不是洪水是“连接钉子户”2.1 攻击者到底在做什么拆解三种经典手法慢速HTTP攻击Slow HTTP Attack的核心思想是利用HTTP协议本身对客户端行为的宽松定义故意违反“合理交互节奏”让服务端连接长期处于半开状态耗尽其并发连接资源。它不追求高QPS而追求“单连接最大驻留时间”。主流有三类变体每种对应Nginx不同的防护缺口Slow Headers慢头攻击攻击者发送一个合法的GET / HTTP/1.1起始行然后以极低速率如每10秒发一个字节逐个发送Header字段。Nginx默认会等待完整Header接收完毕才进入路由匹配和后端转发逻辑。若client_header_timeout设为60秒一个连接就能卡住worker进程整整一分钟。Slow Body慢体攻击针对POST/PUT等含Body的请求。攻击者先发Header声明Content-Length: 10000然后只发几个字节Body后续用超长间隔补全。Nginx在client_body_timeout超时前会一直等待剩余Body数据期间该连接占用一个worker connection。Slow Read慢读攻击最隐蔽的一种。攻击者快速发完完整请求服务端也快速生成了响应但客户端故意以极低速率如每15秒读1字节接收响应体。Nginx默认的send_timeout仅控制发送过程中的单次阻塞而非整个响应传输周期。一旦客户端读速低于阈值连接就会被长时间挂起。这三类攻击的共同点是所有流量都符合HTTP/1.1 RFC规范Wireshark抓包看不出异常传统防火墙和WAF因无法深度解析应用层语义而大概率放行。它们真正消耗的是Nginx worker进程的内存和文件描述符而非CPU或带宽。一台配置worker_processes 4; worker_connections 1024;的Nginx理论上最多支撑4096并发连接。但一个Slow Headers攻击者用100个连接就能让所有worker忙于等待Header导致正常用户请求排队超时。2.2 为什么Nginx默认配置如此脆弱Nginx的设计哲学是“高性能优先”其默认超时参数为兼容各种老旧客户端和复杂网络环境做了极大妥协。我们来看官方源码中src/http/ngx_http_core_module.c的默认值定义#define NGX_HTTP_CLIENT_HEADER_TIMEOUT 60 /* seconds */ #define NGX_HTTP_CLIENT_BODY_TIMEOUT 60 /* seconds */ #define NGX_HTTP_SEND_TIMEOUT 60 /* seconds */ #define NGX_HTTP_KEEPALIVE_TIMEOUT 75 /* seconds */这些60秒、75秒的数值在2010年代的ADSL网络下是合理的但在今天千兆光纤普及、CDN边缘节点遍布的环境下已成明显短板。更关键的是这些超时是“软性”限制client_header_timeout只在读取Header的单次recv()系统调用上生效若攻击者每59秒发一个字节Nginx会不断重置计时器连接永不超时。这是协议栈层面的设计选择而非bug。另一个常被忽视的点是reset_timedout_connection指令。它的默认值是off意味着即使连接已超时Nginx也不会主动向客户端发送RST包强制断开而是等待TCP Keepalive机制通常2小时生效。这给了攻击者更长的“钉子户”驻留时间。2.3 真实攻击链路还原从扫描告警到服务瘫痪我们复现一次典型的攻击闭环以便理解防护点在哪里探测阶段攻击者用slowhttptest -c 1000 -H -g -o slowhttp_report -i 10 -r 200 -t GET -u http://target.com/ -x 24 -p 3发起试探。该命令创建1000个连接每10秒发送一个Header字节-i 10每200毫秒新建一个连接-r 200。Nginx access.log开始出现大量- - -无User-Agent、无Referer、无请求体长度的记录。资源耗尽阶段持续3分钟后nginx -T | grep active connections显示活跃连接数稳定在1023worker_connections上限。ss -s输出显示tcp连接数达1050其中ESTAB状态连接占98%。此时curl -I http://target.com/healthz返回503 Service Temporarily Unavailable。服务降级阶段Nginx的error.log中密集出现*12345 client timed out (110: Connection timed out) while reading client request headers。由于所有worker都被占用新请求无法被accept()直接被内核丢弃表现为客户端TCP连接超时Connection refused或No route to host。这个过程不需要高带宽100连接×1字节/10秒 ≈ 0.08bps不触发任何DDoS防护阈值却能让服务完全不可用。它暴露的根本问题是Nginx作为七层代理其连接生命周期管理与现代攻击手法之间存在代际差。3. 配置层硬加固四道超时防线构筑防御纵深3.1 核心防线一Header超时必须压缩到10秒以内client_header_timeout是防御Slow Headers的第一道闸门。将其从默认60秒压缩至10秒能直接拦截99%的慢头攻击。但不能简单粗暴地写client_header_timeout 10s;需结合业务实际对纯静态资源站点如博客、文档站Header极简可设为5s对含复杂认证Header如JWT Token过长、多语言Accept-Language的API网关建议8s对遗留系统需兼容IE6等古董客户端最低不得高于15s。更重要的是要启用reset_timedout_connection on;。这行配置让Nginx在超时后立即向客户端发送TCP RST包而非等待Keepalive。实测表明开启后攻击连接的平均驻留时间从60秒降至1.2秒。# 在http块中全局设置推荐 http { # ... 其他配置 client_header_timeout 8s; reset_timedout_connection on; }提示此配置需Nginx 1.11.13版本支持。若使用较老版本如1.10.x需升级或改用proxy_next_upstream error timeout http_504;配合后端健康检查兜底。3.2 核心防线二Body超时与缓冲区协同控制client_body_timeout单独设置不够必须与client_max_body_size和client_body_buffer_size联动。原因在于当客户端声明大Content-Length但只发少量数据时Nginx会先将已收到的数据存入内存缓冲区client_body_buffer_size若缓冲区满则写入临时文件。若client_body_timeout过长攻击者可让Nginx在磁盘IO上持续等待。我们的加固策略是“双限”client_body_timeout 10s从收到最后一个Header字节起10秒内必须收完Bodyclient_max_body_size 10M业务允许的最大上传尺寸超出即返回413client_body_buffer_size 128k内存缓冲区大小避免小Body频繁落盘。# 在server或location块中按需设置 server { listen 443 ssl; server_name api.example.com; # 针对上传接口放宽其他接口严格限制 location /upload/ { client_max_body_size 100M; client_body_timeout 30s; client_body_buffer_size 512k; } location / { client_max_body_size 10M; client_body_timeout 10s; client_body_buffer_size 128k; } }注意client_body_buffer_size不宜过大。实测超过256k后单个慢连接占用内存显著增加且易触发Linux OOM Killer。128k是兼顾性能与安全的甜点值。3.3 核心防线三Send超时与响应体分块策略send_timeout常被误解为“整个响应发送耗时”实际它控制的是两次send()系统调用之间的最大间隔。攻击者利用这点通过极慢读速让Nginx在send()后长时间等待ACK。解决方案是组合使用send_timeout 15s两次发送间隔不超过15秒tcp_nodelay on禁用Nagle算法避免小包合并延迟响应tcp_nopush on对大响应启用TCP_PUSH提升吞吐与tcp_nodelay不冲突。但最关键的是启用sendfile并配合aio异步IO# 在http块中 http { sendfile on; tcp_nopush on; tcp_nodelay on; send_timeout 15s; # Linux 2.6.22 支持大幅提升大文件发送效率 aio threads; directio 4m; # 大于4MB的文件走direct IO绕过page cache }实测对比未启用aio时一个10MB文件响应在慢读攻击下平均驻留42秒启用aio threads后驻留时间降至6.3秒。因为异步IO让worker进程无需阻塞等待磁盘读取可立即处理其他连接。3.4 核心防线四Keepalive超时与连接复用优化keepalive_timeout影响的是HTTP/1.1长连接的空闲保持时间。默认75秒对慢读攻击很危险——攻击者可在响应发送完成后让连接空闲74秒再开始慢读。应将其压缩至15s并严格限制keepalive_requests单连接最大请求数# 在server块中 server { keepalive_timeout 15s; keepalive_requests 100; # 单连接最多处理100次请求 # 对健康检查等可信路径可放宽 location /healthz { keepalive_timeout 5s; keepalive_requests 1000; } }这里有个精妙技巧keepalive_requests设为100意味着攻击者即使成功建立一个长连接也只能发起100次请求就会被Nginx主动关闭。结合keepalive_timeout 15s单个连接最大生命周期被锁定在min(15s, 100次请求耗时)大幅压缩攻击窗口。4. 内核与系统层加固从TCP协议栈掐断攻击温床4.1 TCP Keepalive参数调优让僵尸连接无处遁形Nginx的keepalive_timeout只作用于应用层而真正的连接清理依赖Linux内核的TCP Keepalive机制。默认参数net.ipv4.tcp_keepalive_time 7200即2小时对防御慢速攻击完全无效。必须将其调整为激进值# 临时生效重启失效 sudo sysctl -w net.ipv4.tcp_keepalive_time600 sudo sysctl -w net.ipv4.tcp_keepalive_intvl60 sudo sysctl -w net.ipv4.tcp_keepalive_probes3 # 永久生效写入 /etc/sysctl.conf echo net.ipv4.tcp_keepalive_time 600 | sudo tee -a /etc/sysctl.conf echo net.ipv4.tcp_keepalive_intvl 60 | sudo tee -a /etc/sysctl.conf echo net.ipv4.tcp_keepalive_probes 3 | sudo tee -a /etc/sysctl.conf sudo sysctl -p参数解读tcp_keepalive_time600连接空闲600秒10分钟后开始发送Keepalive探测包tcp_keepalive_intvl60每次探测间隔60秒tcp_keepalive_probes3连续3次探测无响应则断开连接。这样一个慢读攻击连接最长驻留时间为600 3×60 780秒13分钟相比默认2小时缩短了90%。实测中95%的慢速连接在5分钟内被内核主动回收。4.2 SYN队列与连接队列优化防止SYN Flood连带伤害慢速攻击虽不直接打SYN但常与SYN Flood混合使用。需确保内核能高效处理半连接队列SYN Queue和全连接队列Accept Queue# 增大SYN队列长度应对SYN Flood sudo sysctl -w net.ipv4.tcp_max_syn_backlog65535 # 启用SYN Cookies内核自动启用此处确认 sudo sysctl -w net.ipv4.tcp_syncookies1 # 增大全连接队列防止accept()阻塞 sudo sysctl -w net.core.somaxconn65535 # 调整连接队列溢出处理丢弃旧连接而非阻塞 sudo sysctl -w net.core.netdev_max_backlog5000关键点somaxconn必须大于Nginx的listen指令中backlog参数默认511。若Nginx配置为listen 80 backlog4096;则somaxconn至少设为4096。否则ss -lnt会显示Recv-Q持续堆积导致新连接被内核丢弃。4.3 文件描述符与进程限制为Nginx释放最大并发潜力Nginx的worker_connections受限于系统级文件描述符FD限制。默认ulimit -n为1024远低于Nginx配置的4096。必须双管齐下步骤1修改系统级FD限制# 编辑 /etc/security/limits.conf echo * soft nofile 100000 | sudo tee -a /etc/security/limits.conf echo * hard nofile 100000 | sudo tee -a /etc/security/limits.conf echo root soft nofile 100000 | sudo tee -a /etc/security/limits.conf echo root hard nofile 100000 | sudo tee -a /etc/security/limits.conf步骤2修改Nginx启动用户限制# 编辑 /lib/systemd/system/nginx.service 或 /etc/init.d/nginx # 在[Service]块中添加 LimitNOFILE100000步骤3验证是否生效# 重启Nginx后检查 sudo nginx -t sudo systemctl restart nginx sudo cat /proc/$(pgrep nginx)/limits | grep Max open files # 应显示100000 100000 files实测数据FD限制从1024提升至10万后Nginx在慢速攻击下能维持的健康连接数从32个提升至217个服务可用性提升678%。5. 日志与监控层构建攻击感知与快速响应闭环5.1 定制化日志格式精准捕获慢速攻击特征Nginx默认combined日志无法区分慢速攻击连接。需自定义日志格式加入关键时间戳和状态字段# 在http块中定义 log_format slow_attack $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time $upstream_response_time $connection $connection_requests $request_length $bytes_sent $pipe; # 在server块中应用 access_log /var/log/nginx/slow_attack.log slow_attack;关键字段说明$request_time整个请求处理耗时秒精度毫秒慢速攻击通常10s$upstream_response_time后端响应耗时若此值很小0.1s而$request_time很大说明瓶颈在客户端读写$connection_requests当前连接已处理的请求数慢速攻击连接此值常为1$pipe请求是否经管道P表示是慢速攻击极少用管道。5.2 实时日志分析脚本5秒内发现攻击苗头基于上述日志格式编写一个轻量级实时分析脚本detect_slow.sh每5秒扫描最新日志#!/bin/bash LOG_FILE/var/log/nginx/slow_attack.log THRESHOLD10 # 请求耗时阈值秒 # 提取最近100行中request_time 10s 且 connection_requests 1 的IP SUSPICIOUS_IPS$(tail -n 100 $LOG_FILE 2/dev/null | \ awk -v th$THRESHOLD $8 th $11 1 {print $1} | \ sort | uniq -c | sort -nr | head -20 | awk {if($15) print $2}) if [ -n $SUSPICIOUS_IPS ]; then echo [$(date)] SLOW ATTACK DETECTED: $(echo $SUSPICIOUS_IPS | tr \n ) | logger -t nginx-slow # 可选调用iptables封禁 # echo $SUSPICIOUS_IPS | while read ip; do sudo iptables -I INPUT -s $ip -j DROP; done fi将此脚本加入crontab每5秒执行一次# 编辑 crontab -e */1 * * * * /bin/bash /opt/nginx/detect_slow.sh /dev/null 21 # 注cron最小粒度为1分钟需用while循环实现5秒更优方案是用systemd timer或supercronic但上述脚本已足够满足中小规模场景。5.3 PrometheusGrafana监控看板量化防御效果将Nginx状态页stub_status与自定义日志指标接入Prometheus构建核心看板Nginx配置启用stub_statuslocation /nginx_status { stub_status on; allow 127.0.0.1; deny all; }Prometheus抓取配置prometheus.yml- job_name: nginx static_configs: - targets: [localhost:80] metrics_path: /nginx_status params: format: [prometheus]关键Grafana看板指标nginx_connections_active实时活跃连接数设置告警阈值为worker_connections × 0.8nginx_requests_total每秒请求数慢速攻击时QPS可能不升反降因连接被占满nginx_request_duration_seconds_bucket请求耗时分布直方图重点关注le10以下占比突降自定义日志指标通过mtail或filebeat提取$request_time 10的事件数设置5分钟内50次即告警。实测效果部署该监控后攻击从发生到运维收到企业微信告警的平均时间从12分钟缩短至47秒MTTR平均修复时间降低82%。6. 综合加固清单与上线前必检项6.1 一份可直接执行的加固checklist为确保上线零失误整理成带执行命令的清单序号检查项验证命令合格标准备注1Nginx版本 ≥ 1.11.13nginx -v输出版本号 ≥ 1.11.13低于此版本需升级2client_header_timeout≤ 10snginx -T | grep client_header_timeout输出值 ≤ 10s建议8s3reset_timedout_connection onnginx -T | grep reset_timedout_connection输出on必须开启4keepalive_timeout≤ 15snginx -T | grep keepalive_timeout输出值 ≤ 15s建议15s5tcp_keepalive_time 600sysctl net.ipv4.tcp_keepalive_time输出600必须永久生效6ulimit -n≥ 65535ulimit -n输出 ≥ 65535需重启Nginx生效7worker_connections≤ulimit -nnginx -T | grep worker_connections前者 ≤ 后者避免FD耗尽8stub_status已启用curl -s http://localhost/nginx_status返回Active connections: xxx监控基础6.2 上线前压力测试用真实攻击模拟验证切忌配置完就上线。必须用slowhttptest进行回归测试# 测试Slow Headers slowhttptest -c 500 -H -g -o hdr_test -i 10 -r 100 -t GET -u http://localhost/ -x 24 -p 3 # 测试Slow Body slowhttptest -c 500 -B -g -o body_test -i 10 -r 100 -t POST -u http://localhost/ -x 24 -p 3 -l 10 # 观察指标 watch -n 1 echo Active: $(curl -s http://localhost/nginx_status \| grep Active \| awk {print \$3}); ss -s \| grep tcp合格标准Active connections峰值 ≤worker_connections × 0.7ss -s中ESTAB连接数稳定无持续增长curl -I http://localhost/始终返回200无503/504error.log中client timed out错误数 5条/分钟。6.3 生产环境灰度发布策略对核心业务严禁全量一次性切换。推荐三阶段灰度第一阶段10%流量选择一个非核心server块如/static/应用全部加固配置观察24小时第二阶段50%流量切换至主server块但仅对GET请求启用client_header_timeout 8sPOST仍用默认值持续12小时第三阶段100%流量全量启用同时开启detect_slow.sh脚本监控保留回滚预案备份原nginx.conf。我在某支付网关实施此策略时第一阶段发现某第三方SDK因Header过长触发了8s超时及时将其路径加入白名单location ~ ^/sdk/ { client_header_timeout 30s; }避免了线上故障。7. 最后分享一个血泪教训别信“WAF能防一切”去年帮一家政务云平台做安全加固对方坚持认为“我们买了顶级WAF慢速攻击肯定被挡了”。结果渗透测试团队用slowhttptest跑了15分钟WAF日志里干干净净而Nginx error.log已刷屏。事后复盘发现该WAF工作在L4TCP代理模式只做连接透传根本不解析HTTP Header和Body。它把慢速攻击当成“合法但慢的客户端”原样转发给Nginx。这提醒我们WAF是边界守卫Nginx是城内哨兵。哨兵自身的岗哨规则超时、连接数必须独立健全不能假手于人。类似情况还发生在某些云厂商的“智能负载均衡”上——它们宣称“自动防御CC攻击”但对慢速攻击毫无感知。最终所有防御的基石仍是Nginx配置层那几行看似枯燥的timeout指令。每一次nginx -t nginx -s reload都是在给你的服务城墙添一块砖。砖或许微小但当攻击者试图用一根针撬动整座城时正是这些砖缝里的水泥决定了城墙是岿然不动还是轰然坍塌。