tcp_nopush需配合sendfile on才能生效,仅在零拷贝路径下合并响应头与文件数据为满载包以减少小包;必须关闭tcp_nodelay、禁用gzip、确保content-length明确且缓冲区充足,按location精细化配置。

tcp_nopush 不是单独开个开关就能起效的参数,它本质是启用 Linux 的 TCP_CORK 行为,目的是让内核把 HTTP 响应头和文件数据攒成一个满载 TCP 包再发出去,从而减少小包数量、提升单包利用率。但它只在特定路径下才真正工作——核心前提就一条:必须走 sendfile 零拷贝路径。
下面分几个关键点说清楚怎么配、为什么这么配、以及容易踩的坑。
必须搭配 sendfile on 才生效
tcp_nopush on 单独写在配置里完全没用。Nginx 只有在启用 sendfile on 且响应体来自真实磁盘文件(比如用 root 或 alias 指向本地路径)时,才会把响应头 + 文件开头数据交由内核一次性组装进 TCP 报文段。
如果用了 proxy_pass、gzip on、FastCGI、Lua 脚本或任何需要用户态读取/处理的逻辑,Nginx 就退回到普通 read() + write() 模式,此时 tcp_nopush 直接被忽略,error log 里会出现 "tcp_nopush is ignored" 提示。
所以基础组合必须是:
sendfile on;tcp_nopush on;
而且这两个指令要写在具体服务静态文件的 location 块里,不能只靠 http 块继承。
只对明确长度的静态文件有效
tcp_nopush 生效的前提是 Nginx 能预知响应体总大小,也就是响应头中必须包含准确的 Content-Length。常见满足条件的场景有:
- 直接
root /data;+location ~ \.mp4$ { ... }这类纯静态服务 - 后端代理返回带
Content-Length且非Transfer-Encoding: chunked的响应
不满足的情况包括:
- PHP 或 Node.js 流式输出(边生成边发)
- Nginx 自己加了
add_header导致长度重算失败 - 使用
proxy_buffering off或缓冲区太小,无法缓存完整响应
配套参数必须对齐
光有 sendfile 和 tcp_nopush 还不够,下面这些设置要同步到位:
tcp_nodelay off;tcp_nopush和tcp_nodelay在内核层面逻辑冲突(一个攒包,一个禁 Nagle 立即发),必须显式关闭后者,避免被上级配置意外继承。gzip off;
gzip 压缩必须在用户态完成,一开就强制禁用sendfile,tcp_nopush自然失效。output_buffers 2 128k;(可选但推荐)
默认2 32k缓冲太小,大文件首段可能触发提前冲刷,加到128k更适配高清图、短视频等常见首段体积。sendfile_max_chunk 512k;
防止单次sendfile()调用阻塞事件循环,也匹配多数大资源的前半段大小。
按文件类型精细化配置,别全局开
不要在 http 块里直接写 tcp_nopush on。API 接口、WebSocket、HLS 的 .m3u8、甚至带自定义 header 的 HTML 页面,都不走 sendfile 路径,开了反而白占配置,还可能干扰其他逻辑。
推荐写法示例(专用于大文件下载):
location ~ \.(mp4|webm|zip|iso|tar\.gz|dmg|psd|tiff)$ {
root /data/files;
sendfile on;
tcp_nopush on;
tcp_nodelay off;
gzip off;
expires 30d;
}
对于 JS/CSS/woff2 等中小关键资源,可以考虑同时开 tcp_nopush on 和 tcp_nodelay on,兼顾首字节时间和包合并效率;而 的 <code>favicon.ico,则只需 tcp_nodelay on 效果最明显。
怎么验证真生效了
别只看配置有没有写。运行时确认三件事:
-
strace -e trace=sendfile -p $(pgrep nginx):看到 worker 进程调用sendfile()系统调用 - Wireshark 抓包:HTTP 状态行、响应头与文件开头数据(如 MP4 的
ftypmagic bytes)是否出现在同一个 TCP 报文段 - 检查 error log:确保没有
tcp_nopush is ignored类警告
不复杂但容易忽略











