sendfile 与 tcp_nopush 必须同时启用且满足三个条件才生效:当前 location 显式开启 sendfile、响应为真实磁盘文件、响应头含 Content-Length;否则退回到 read/write 模式。

sendfile 与 tcp_nopush 必须同时启用、且仅在满足三个硬性条件的 location 中才真正生效;否则配置了也白配,Nginx 会静默退回到 read/write 模式。
为什么单独开 sendfile 或 tcp_nopush 都没用
tcp_nopush 不是“延迟发送开关”,它只是告诉内核:等响应头写完、且后续数据(比如文件开头)能凑满一个 TCP 段(MSS ≈ 1448 字节)时,再一起发出去。这个“后续数据”必须来自 sendfile 路径——即内核直接从磁盘页缓存搬进 socket 发送队列,不经过用户态缓冲区。
若 sendfile off,Nginx 先 read() 到用户空间 buffer,再 write() 到 socket,此时 tcp_nopush 完全被忽略,Nginx 日志里会出现 tcp_nopush is ignored 警告。
常见误判点:
- 看到配置里写了
tcp_nopush on就以为生效了 - 在 proxy_pass 场景下全局开了
sendfile on,但后端返回的是 chunked 编码或没带Content-Length - 启用了
gzip on,哪怕只压缩 JS/CSS,也会导致整个请求链路禁用 sendfile
必须满足的三个运行时前提
配置写了不算数,Nginx 在每次响应时都会动态判断是否走零拷贝路径。以下三点缺一不可:
-
sendfile on已在当前 location 块中显式启用(继承自 server/http 块可能被覆盖) - 请求命中的是真实磁盘文件:例如
root /data/assets+location ~ \.zip$,且文件存在、可读、未被 SELinux 拦截 - 响应头中必须含
Content-Length:Nginx 需提前知道文件大小,才能调用sendfile()系统调用;若后端动态生成或流式响应无长度头,自动 fallback 到read()/write()
注意:etag、expires、add_header 这类纯 header 操作不影响 sendfile;但 sub_filter、access_by_lua_block、proxy_buffering on 会强制退出零拷贝路径。
tcp_nodelay off 是 tcp_nopush 生效的关键配合项
Linux 下 tcp_nopush on 实际启用的是 TCP_CORK,它的行为是“攒包”,不是“延迟”。而 tcp_nodelay on 会强制立即发包(禁用 Nagle),二者逻辑冲突。
所以必须确保:
-
tcp_nodelay off(默认值就是 off,但建议显式写出,避免被上级块继承on) - 不要在同一个 location 中混用
tcp_nodelay on和tcp_nopush on,后者会被前者直接废掉 - 对视频首帧加载特别敏感的场景(如 HLS 的第一个 ts),可考虑把 m3u8 单独剥离到另一个不启用
tcp_nopush的 location,避免因凑包带来毫秒级首包延迟
如何验证是否真正在用零拷贝
别信配置,看系统调用:
- 用
strace -p $(pgrep nginx) -e trace=sendfile,read,write抓 worker 进程,下载一个 mp4 文件,观察是否出现sendfile(调用;若全是read(和write(),说明被 gzip、chunked 或权限问题拦住了
- 用
ss -i 查看连接的cwnd和retrans,零拷贝+tcp_nopush 后小包数量应明显下降,重传率趋近于 0 - 对比开启前后 top 中 nginx worker 的 %CPU:大文件高并发下,CPU 使用率通常下降 30%–50%
最容易被忽略的一点:即使所有配置都对,若文件系统挂载时用了 noatime 以外的选项(比如 relatime 或未禁用 barrier),也可能让 sendfile 的实际性能打折扣——这不是 Nginx 层能控制的,得查 /proc/mounts 和内核日志。










