必须在http块中成套配置sendfile on、tcp_nopush on、tcp_nodelay off,缺一不可;因sendfile需完整响应链路可控(本地文件、明确Content-Length)才生效,proxy_pass/gzip等会强制回退至read()+write();tcp_nopush仅在sendfile路径下起作用,依赖output_buffers调大及tcp_nodelay off配合,否则无效或恶化性能。

必须在 http 块里成套配置 sendfile on、tcp_nopush on、tcp_nodelay off,缺一不可;单独开 sendfile 或只配 tcp_nopush 都无效。
为什么 sendfile 单独开启没用
它只是启用内核零拷贝的开关,但实际是否走这条路径,取决于整个响应链路是否“可控”:Nginx 必须能直接读本地磁盘文件(非代理、非动态生成、非 gzip 后处理),且响应头中必须含明确 Content-Length。一旦用了 proxy_pass、gzip on、sub_filter 或返回 Transfer-Encoding: chunked,Nginx 就自动回退到 read()+write() 模式,sendfile 彻底失效。
常见踩坑点包括:
- 在
location /static/里开了sendfile on,但上游是proxy_pass http://backend—— 此时sendfile不生效 - 同时开了
gzip on和sendfile on—— gzip 会强制解压重写响应体,破坏零拷贝路径 - 用
alias指向符号链接,而内核未启用follow_symlinks(默认关闭)—— 文件读取失败,fallback 到用户态
tcp_nopush 依赖 sendfile 才真正起作用
tcp_nopush 不是独立加速器,它只在 sendfile 路径下才接管 TCP 缓冲行为:等响应头 + 文件数据一起填满 TCP 发送缓冲区,再合并发出。否则它只是个摆设。
要让它稳定工作,还需配套两项:
-
output_buffers 2 128k:默认2 32k对大 JS/CSS/图片常不够,缓冲区过早溢出会导致提前发包,抵消tcp_nopush效果 -
tcp_nodelay off:必须关掉。若设为on,会强制立即发送未填满的包,和tcp_nopush的“攒包”逻辑直接冲突
错误配置示例:tcp_nopush on; tcp_nodelay on; —— 这组搭配会让 Nginx 在同一连接上反复“攒又立刻发”,小包数量不降反升。
代理转发场景下 tcp_nopush 容易静默失效
当 Nginx 作为反向代理拉取静态资源(比如从 MinIO 或内部文件服务),tcp_nopush 是否生效,完全取决于后端响应是否“可预判长度”:
- 后端必须返回
Content-Length,不能是chunked编码 - Nginx 必须缓存完整响应体再转发(即
proxy_buffering on,且proxy_buffer_size+proxy_buffers总和 ≥ header + body 大小) - 若后端流式生成响应(如 Python 的
yield返回大文件),Nginx 无法预知长度,tcp_nopush自动降级为无效
验证方法:用 curl -v 看响应头是否有 Content-Length,再用 tcpdump 抓包观察 TCP 包大小分布 —— 开启后应明显减少
配置位置和继承关系很关键
这三项指令(sendfile、tcp_nopush、tcp_nodelay)都支持 http、server、location 三级作用域,但必须保证它们在同一个作用域层级生效。例如:
错误写法:
http {
sendfile on;
tcp_nopush on;
}
server {
tcp_nodelay on; // ← 这里单独设为 on,会覆盖 http 块的 off,导致冲突
}
正确写法(推荐统一在 http 块):
http {
sendfile on;
tcp_nopush on;
tcp_nodelay off;
output_buffers 2 128k;
}
注意:tcp_nodelay 默认值就是 on,所以如果你没显式关掉它,它大概率正开着——这正是多数人配了 tcp_nopush 却没效果的根源。
最易被忽略的一点:这些优化只对真实静态文件有效,对 proxy_pass、fastcgi_pass、grpc_pass 等动态路径完全不触发。别指望加几行配置就能让 PHP 接口变快。











