tcp_nopush 必须与 sendfile 协同启用才有效,单独开启无效;其核心是减少小包、提升载荷率,适用于首屏快、带宽高、延迟适中的静态图片分发场景。

在分发海量高清图片这类大体积静态资源时,tcp_nopush 必须与 sendfile 协同启用才有效,单独开启 tcp_nopush 不起作用。它的核心价值是减少小 TCP 包数量、提升单包载荷率,尤其适合首屏加载快、带宽利用率高、延迟容忍度适中的场景。
为什么 tcp_nopush 依赖 sendfile?
tcp_nopush 的机制是:让内核把响应头和第一个数据块“攒在一起”,合并进一个 TCP 报文发出。但这个“攒”动作的前提是——数据已准备好、可一次性送入发送队列。而 sendfile 正是提供这种确定性传输路径的关键:
- 普通 read/write 模式下,Nginx 需多次从用户态读取再写入 socket,数据是分段到达内核发送缓冲区的,tcp_nopush 无法识别“完整首块”
- sendfile 启用后,文件内容直接由内核从磁盘描述符复制到 socket 描述符,响应头+文件开头部分能被内核视为逻辑连续的数据流,此时 tcp_nopush 才真正触发合并行为
- 若未启用 sendfile(如启用了 gzip、proxy_buffering 或后端未返回 Content-Length),tcp_nopush 自动降级为无效配置
只对图片路径精准启用,避免误伤其他流量
全局开启 tcp_nopush + sendfile 会干扰 API、WebSocket、实时推送等低延迟服务。应按 MIME 类型或后缀做 location 级控制:
- 匹配常见图片格式:location ~* \.(jpg|jpeg|png|webp|gif|svg|avif)$
- 关闭该路径下的 gzip:gzip off;防止压缩强制走用户态流程,导致 sendfile 失效
- 确保后端(如对象存储直传)返回明确的 Content-Length,否则 Nginx 会退回到 chunked 编码,sendfile 被绕过
- 若图片由上游应用动态生成(如 PHP/Python 图片处理服务),需确认其响应头含 Content-Length,且 Nginx 未开启 proxy_buffering(或设为 off)以避免缓冲截断
配套参数必须对齐,否则优化失效
tcp_nopush 不是独立开关,它与 tcp_nodelay、buffer 分配、连接模型存在强耦合:
- tcp_nodelay 必须为 off:两者互斥。开启 tcp_nodelay 会强制立即发包,直接抵消 tcp_nopush 的“攒包”效果
- client_header/body_buffer_size 建议调至 16k:避免因请求头过大(如含长 Cookie 或 SNI)触发 400 错误,间接阻断图片请求
- 静态图片 location 内显式设置 sendfile on; tcp_nopush on; tcp_nodelay off;
- 若使用 proxy_pass 中转图片(如代理到 CDN 回源),需加 proxy_buffering off; proxy_http_version 1.1; 并确保 upstream 支持 keepalive
验证是否真正生效的三个关键点
配置写完不等于优化落地,需逐项确认运行时状态:
- 用 curl -I 请求一张图片,检查响应头中是否有 Content-Length,且无 Transfer-Encoding: chunked
- 用 tcpdump 抓包观察前两个 TCP 包:第一个应同时含 HTTP 响应头 + 文件开头若干 KB 数据(而非纯 header),第二个包才是后续数据
- 对比开启前后 top 中 nginx worker 进程的 %CPU:sendfile + tcp_nopush 组合通常可降低 30%–50% CPU 占用,尤其在千兆以上带宽满载时更明显











