tcp_nopush必须依赖sendfile才能生效,因为只有sendfile启用时,内核才能将响应头与文件起始内容视为逻辑连续的数据流并合并发送;若使用read/write模式或启用gzip、proxy_pass等,tcp_nopush自动失效。

tcp_nopush 本身不积攒数据,它只是告诉内核:“等响应头发完、第一个数据块也准备好时,先别急着发,看看能不能和头一起塞进一个 TCP 包里。”这个“等一等”的动作,只有在 sendfile 启用后才有意义——因为只有 sendfile 能让内核一次性拿到响应头 + 文件开头部分,形成逻辑连续的数据流。
为什么必须依赖 sendfile?
普通 read/write 模式下,Nginx 要先把文件读进用户态缓冲区,再调用 write() 发给 socket。这个过程是分段的:先写响应头,再多次 read + write 文件内容。内核看到的是离散的 write() 调用,无法判断哪次 write 是“第一个数据块”,tcp_nopush 就无从触发。
sendfile 则完全不同:Nginx 只需一次系统调用,把文件描述符和 socket 描述符交给内核。内核自己完成磁盘到网卡的数据搬运,响应头和文件起始内容在内核发送队列中天然连贯。这时 tcp_nopush 才能生效,让内核把这两部分合并发出。
“积攒”不是延迟,而是对齐 MSS
tcp_nopush 实际启用的是 Linux 的 TCP_CORK 机制,它的行为是:
- 只在响应头已写入、且后续待发数据(通常是文件前 1448–1460 字节)能接近 MSS 时才暂存
- 不设固定等待时间,也不阻塞后续数据;一旦凑够或遇到 flush 条件(如 sendfile 结束、连接关闭),立即发出
- 目标是减少小包数量,避免 Nagle 算法与延迟 ACK 叠加造成的多倍微小报文
哪些情况会让这个优化失效?
只要破坏了“内核直达 + 头体连贯”这个前提,tcp_nopush 就退化为无效配置:
- gzip on:压缩必须在用户态做,强制走 read/write 路径
- proxy_pass 动态转发:除非后端返回 Content-Length 且 Nginx 启用 proxy_buffering off,否则无法触发 sendfile
- location 中用了 sub_filter、access_by_lua_block 等模块:内容被用户态修改,零拷贝路径中断
- 响应缺失 Content-Length:Nginx 不知道文件多大,不敢用 sendfile,降级为常规读写
配置时的关键细节
这不是开个开关就能见效的设置,需要配合约束条件落地:
- tcp_nopush 必须放在 sendfile on 之后,且两者作用域一致(推荐在 http 块设全局默认,再在 location 中按需覆盖)
- 对明确的大静态资源(如 .mp4、.zip)单独启用,同时关 gzip、确保 root 或 alias 指向真实文件路径
- tcp_nodelay 和 tcp_nopush 在逻辑上互斥:前者禁用 Nagle 立即发小包,后者主动攒包;通常 sendfile 场景选 tcp_nopush,非 sendfile 场景选 tcp_nodelay
- sendfile_max_chunk 建议设为 512k,防止单次 sendfile 占用事件循环过久,影响其他请求处理










