关键是要让client_body_buffer_size略大于95%请求体大小并避开25%余量临界点;需同步设置client_max_body_size≥缓冲区值、确保client_body_temp_path目录权限和空间充足、禁用client_body_in_file_only。

要让 Nginx 处理 POST 请求体时不写临时文件,关键不是把 client_body_buffer_size 设得尽可能大,而是让它略大于你业务中 95% 以上请求体的实际大小,并避开 Nginx 内部 25% 安全余量触发的临界点。
为什么设成“刚好够大”反而会写磁盘?
Nginx 实际分配缓冲区时会做一次向上取整:先取配置值,再加 25%(即 size += size >> 2)。如果请求是已知长度(非 chunked),且 Content-Length 小于这个带余量的值,才用真实长度;否则就按原配置值分配。
这意味着:
- 你设了
client_body_buffer_size 256k,Nginx 实际尝试分配的是 320KB(256 + 64) - 但若某个请求体正好是 257KB,它仍小于 320KB,所以全程走内存
- 可一旦实测最大请求体是 319KB,而你只设了 256k,那 319KB > 320KB?不成立——319
- 真正危险的是:实测最大为 320KB,你设 256k → 256k × 1.25 = 320KB → 刚好卡在边界 → Nginx 会退回到用原始配置值 256k 分配,320KB 超出部分就写磁盘
所以“设到 P99 大小”不够,得上探到 P99 × 1.33 左右,或直接跳到下一个常见档位(如 384k、512k)。
哪些场景下必须调大 client_body_buffer_size?
不是所有上传都值得调。重点盯这三类:
-
带 Base64 图片的表单提交:一个 200KB 的头像转成 Base64 后约 270KB,
client_body_buffer_size至少 384k 才稳 - JSON API 携带 JWT 或长 payload:含签名、声明、嵌套对象的 JWT 可达 8–12KB,加上业务字段后常超默认 8k,设 16k 或 32k 更稳妥
-
分片上传的首请求(含元数据+首片):哪怕单片只有 512KB,首请求可能附带 JSON 描述、校验信息,总长轻松破 1MB,此时需设
client_body_buffer_size 1m并确认client_max_body_size≥ 1m
纯文本表单、无附件的 API 调用,默认 8k 完全够用,没必要动。
调完怎么确认真的没写磁盘?
别只看服务是否正常,要验证行为本身:
- 开 debug 日志:
error_log /var/log/nginx/debug.log debug;,上传后搜client request body buffered in memory—— 出现这条才算成功;若看到write to temp file或client request body temp file,说明仍落盘 - 检查临时目录:
ls -lt /var/lib/nginx/body/(或你设的client_body_temp_path),上传前后文件数应无变化 - 用
strace -p $(pgrep nginx -f worker) -e trace=openat,write -f 2>&1 | grep body,上传过程中不应出现对临时路径的openat调用 - 压测时观察
iostat -x 1中对应磁盘的write/s和await,下降明显才有意义
最容易被忽略的配套动作
单独改 client_body_buffer_size 基本等于白调:
-
client_max_body_size必须在同一作用域(http、server或location)显式设置,且 ≥ 缓冲区值,否则大请求直接 413 -
client_body_temp_path对应目录必须存在、Nginx worker 用户(如www-data)有写权限、磁盘空间充足——即使你不打算让它写,Nginx 启动时也会尝试创建该目录 - 禁用
client_body_in_file_only on(默认是off,但有些模板会误开) - 高并发小上传场景(如 IoT 心跳),反而要把
client_body_buffer_size调小(如 4k),避免每个连接多占几百 KB 内存
真正的瓶颈往往不在缓冲区大小本身,而在你没注意到的 client_body_temp_path 所在磁盘 I/O 已饱和,或者 client_max_body_size 在别的配置块里悄悄覆盖了你的设置。











