client_body_timeout 设为10–20s是安全平衡点:覆盖正常用户2–5秒的准备时间,迫使攻击者每15秒至少发一次数据,避免低于5s误杀或高于30s失效;须配合client_header_timeout、client_max_body_size和reset_timedout_connection才有效。

client_body_timeout 不是用来限制“上传花了多久”,而是卡在“两次发包之间空等了多久”。设成 60s 就等于给 Slow POST 攻击者开了个长期占坑许可证——它真正在防的,是每秒只传 1 字节、却声明要传 100MB 的那种连接。
为什么 client_body_timeout 设成 10–20s 是多数场景的安全平衡点
这个值不是拍脑袋定的,它得同时满足两个现实约束:正常用户能过,攻击者刚伸腿就被绊倒。
- 浏览器或主流 SDK 发起 POST 后,TLS 握手 + DNS 解析 + 应用层准备,一般 2–5 秒内就会开始发 body 数据;设 10s 足够覆盖绝大多数合法行为
- 攻击者若想维持一个慢速连接,必须把间隔控制在
client_body_timeout以内;设 15s 意味着他每 15 秒至少得发一次数据,否则连接被 Nginx 主动RST掉 - 低于 5s 容易误杀:移动网络切网、Wi-Fi 切蜂窝、后台 App 唤醒延迟都可能造成 >3s 的静默,导致大量 408
- 高于 30s 就失去防御意义:攻击者用
--limit-rate 100模拟 100B/s 发送,30s 内只传 3KB,但已足够拖住 worker 连接数
client_body_timeout 单独生效等于没设,必须配齐这三组参数
只调 client_body_timeout,就像只锁门不关窗——攻击者立刻转去卡 client_header_timeout 或靠 client_max_body_size 堆满缓冲区。
-
client_header_timeout 10s:防止 Slow headers,和client_body_timeout形成首尾夹击;header 阶段超时也返回 408,不给攻击者留“先发头再慢慢发体”的窗口 -
client_max_body_size 20M:配合使用。没有它,攻击者可以声明Content-Length: 2G,Nginx 就会按需分配缓冲甚至落盘,最终耗尽磁盘 or 内存;设上限后,超限直接 413,不进 timeout 流程 -
reset_timedout_connection on:关键补丁。默认超时后发FIN等四次挥手,连接卡在TIME_WAIT;设为on后直接发RST,句柄和 socket 立刻释放,Windows 下尤其必要
验证配置是否真起作用:用 curl 模拟攻击比看文档管用
别信日志里有没有 “client timed out”,要亲眼看到连接在预期时间断开、且状态码是 408。
- 模拟慢速 POST:
curl -X POST http://localhost/api -H "Content-Type: application/octet-stream" --data-binary "@/dev/zero" --limit-rate 50 -m 120(50B/s 发送,客户端总超时 120s) - 观察响应:
curl应在约client_body_timeout秒后退出,并打印HTTP/1.1 408 Request Timeout - 查 Nginx error log:
tail -f /var/log/nginx/error.log中应出现类似client timed out (110: Connection timed out) while reading client request body - 注意干扰项:如果看到
413 Request Entity Too Large,说明client_max_body_size先触发了,得调大它再测 timeout
流式上传、断点续传接口不能只靠 client_body_timeout 控制节奏
这类业务本身就需要长间隔发送数据(比如前端每 30 秒上报一个分片进度),硬压 client_body_timeout 到 20s 只会导致频繁重连。
- 正确做法是:Nginx 层设宽松值(如
client_body_timeout 60s),但要求客户端在空闲期主动发心跳包(例如带X-Upload-Heartbeat: 1的空 POST) - 用
location块单独匹配上传路径,加if ($http_x_upload_heartbeat) { return 200; }快速响应心跳,避免走完整 body 解析流程 - 真正校验上传节奏的逻辑必须下沉到应用层:记录每个 upload_id 最后活跃时间,超时未续则服务端主动清理资源
- 忽略这点,就等于把防御责任全甩给 Nginx,而它根本不知道“这个连接还在传第 3 个分片”还是“攻击者又在划水”











