tcp_nopush在小包场景下无效且有害,因其依赖sendfile零拷贝路径才能触发tcp_cork攒包机制,而api等动态小响应不走sendfile;误配会隐式延迟发送,导致首字节延迟飙升,必须关闭tcp_nopush、开启tcp_nodelay以保障低延迟。

海量小包传输场景下,tcp_nopush 应保持 off,tcp_nodelay 必须为 on —— 否则首字节延迟飙升、连接吞吐骤降,且 tcp_nopush 在此场景完全无效。
为什么 tcp_nopush 在小包场景下不仅没用,还会干扰?
tcp_nopush 本质是启用 TCP_CORK(Linux)或 TCP_NOPUSH(FreeBSD),它要求内核“攒够数据再发”,前提是走 sendfile 路径且数据可批量落盘。而海量小包(如 API 返回 JSON、WebSocket ping/pong、SSE event stream、HTTP/1.1 短响应体)全部由 upstream 动态生成,不经过 sendfile,因此该指令压根不触发。
更关键的是:即使误配了 tcp_nopush on,Nginx 也不会报错,但内核可能在某些路径下隐式延迟发送,导致本该立刻发出的 ACK 或小响应卡住几十毫秒——这对高并发低延迟服务是致命的。
- 常见错误现象:
curl -w "%{time_starttransfer}\n" -s http://api.example.com/health延迟从 5ms 跃升至 200ms+;Wireshark 显示大量单字节 TCP 包带 [PSH] 标志,但间隔不稳定 - 使用场景:微服务网关、实时鉴权接口、前端埋点上报、长轮询端点、gRPC-Web 代理
- 验证方式:
ss -i | grep -A1 "port=80"查看连接是否含np(nopush)标记;若无且仍配了tcp_nopush on,说明配置未生效,但已造成心理误判
tcp_nodelay on 是小包传输的硬性前提
tcp_nodelay on 关闭 Nagle 算法,让每个 write() 调用产生的数据立即发出,不等待 ACK 或凑满 MSS。这是应对小包的核心保障,Nginx 默认即为 on,但必须确认它实际作用于目标连接。
- 容易踩的坑:只在
http块写tcp_nodelay on,但监听端口未显式声明 —— 正确写法是listen 80 tcp_nodelay;或listen 443 ssl http2 tcp_nodelay;,否则在某些系统(如旧版 CentOS 内核)上可能不继承 - 性能影响:开启后小包数量上升,但总延迟下降;实测中,QPS 提升 15–30%,P99 延迟降低 50%+(以 wrk -t4 -c100 -d30s 测试典型 JSON 接口)
- 兼容性注意:HTTP/2 下该参数影响减弱(帧调度更精细),但仍建议保留,因 fallback 到 HTTP/1.1 时需兜底
混合部署时如何避免 location 级误配?
当同一 server 同时提供小包 API 和大文件下载(如 /api/ + /downloads/),不能全局开关,必须按路径隔离。错误示例是把 tcp_nopush on 放在 server 块顶层,导致所有子路径继承。
- 正确做法:仅在明确服务静态大文件的
location中启用sendfile on; tcp_nopush on;;其余路径(尤其/api/,/ws/,/events)显式关闭sendfile并确保tcp_nodelay生效 - 典型配置片段:
location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ''; # 不要写 sendfile 或 tcp_nopush } location ~ \.(mp4|zip|iso)$ { root /data/files; sendfile on; tcp_nopush on; tcp_nodelay off; # 注意:此处 off 是配合 nopush 的协同项,与上面的 on 不冲突 } - 容易被忽略的点:
tcp_nodelay off在大文件 location 中是合理选择,但它只对该 location 有效;只要 API 路径没继承它,就不会出问题 —— 关键在于作用域隔离,而非“全局统一”
真正难的是判断哪些流量算“小包”。不要凭文件后缀或路径名猜测,要用 tcpdump -s0 -w api.pcap port 80 and greater 100 抓包后看 payload 大小分布:若 80% 的响应体 tcp_nopush 的尝试都是徒劳,盯紧 tcp_nodelay 的生效位置和连接复用状态即可。











