来源:未知来源 | 发布日期:2026-07-30
Nginx upstream 超时配置底层逻辑
502/504 频发时,盲目加大
proxy_read_timeout会把连接泄漏、队列堆积、DNS 错误隐藏得更久。本文按请求生命周期建立证据链,给出配置、验证、灰度和回滚方法。
四个超时阶段
| 阶段 | 配置 | 说明 |
|---|---|---|
| 建连 | proxy_connect_timeout |
Nginx 到 upstream 的 TCP 建连超时 |
| 发送 | proxy_send_timeout |
向上游发送请求的两个写操作之间间隔 |
| 读取 | proxy_read_timeout |
读取响应的两个读操作之间间隔,非整条请求总耗时 |
| 失败摘除 | max_fails / fail_timeout |
被动摘除,不主动探测健康 |
排查 31 步
1. 确认生效版本与编译模块
nginx -V 2>&1
nginx -v
systemctl status nginx --no-pager
配置项是否可用由实际 Nginx 二进制决定。不同发行版路径和编译模块可能不同。
2. 语法检查并打印完整配置
sudo nginx -t
sudo nginx -T > /var/tmp/nginx-effective.conf
rg -n 'proxy_(connect|send|read)_timeout|upstream|proxy_pass' /var/tmp/nginx-effective.conf
-T 展开包含文件,输出可能含证书路径或内部地址,应按安全要求保管。
3. 区分 502、504 与客户端断开
awk '$9 ~ /^(502|504)$/ {print $4, $7, $9, $10, $11}' /var/log/nginx/access.log | tail -n 100
rg -n 'upstream|timed out|connect\(\) failed|prematurely closed' /var/log/nginx/error.log | tail -n 100
access log 中 $9 是状态码,error log 中的错误文本决定下一步;不要只统计状态码就下结论。
4. 给日志补上 upstream 时间
没有 $upstream_* 字段就无法判断时间耗在哪一段。
log_format upstream_timing '$remote_addr $request $status '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time '
'ua="$upstream_addr" us="$upstream_status"';
access_log /var/log/nginx/access-upstream.log upstream_timing;
request_time 是 Nginx 总处理时间,upstream_response_time 是上游响应时间;多个重试地址时字段可能以逗号分隔。
5. 检查监听端口和本机连通性
ss -lntp | rg ':(80|443|<上游端口>)\b'
curl -sS -o /dev/null -w 'http=%{http_code} connect=%{time_connect} start=%{time_starttransfer} total=%{time_total}\n' \
http://<上游地址>/health
若上游有 Host 路由,增加 -H 'Host: <域名>'。
6. 检查 upstream 配置和失败阈值
upstream app_backend {
least_conn;
server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
keepalive 64;
}
max_fails 是被动失败计数,fail_timeout 既是统计窗口也是临时不可用时间。它不替代应用健康检查。
7. 被动失败包含什么
location /api/ {
proxy_pass http://app_backend;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
}
只对幂等请求开启重试;POST、支付、创建资源等请求若未设计幂等键,重试可能造成重复写入。
8. 看错误日志中的 connect failed
rg -n 'connect\(\) failed' /var/log/nginx/error.log | tail -n 50
ip route get <上游IP>
nc -vz -w 3 <上游IP> <上游端口>
Connection refused:进程没起来或端口未监听No route to host:网络不可达cannot assign requested address:本地端口耗尽
nc 只验证 TCP 建连,不代表 HTTP 可用。
9. 抓一次真实响应头
curl -sv --max-time 10 -H 'Host: <域名>' http://<上游地址>/<路径> \
-o /dev/null 2>&1 | sed -n '/^> /p;/^< /p'
502 可能来自上游本身返回的 502,而非 Nginx 生成。用同一 URI、Host、鉴权和协议复现。
10. connect timeout 理解
location /api/ {
proxy_pass http://app_backend;
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
建连排队或网络黑洞时 proxy_connect_timeout 才起作用。3 秒不是通用值,应由网络基线和故障切换目标决定。设得过大,会让不可达节点占用更多工作连接。
11. send timeout 与 read timeout 区分
rg -n 'upstream timed out.*while (sending request to upstream|reading response header from upstream)' \
/var/log/nginx/error.log | tail -n 100
sending request:优先查请求体、上游接收和网络reading response header:优先查应用线程池、数据库与依赖
12. 大请求设置明确上限
client_max_body_size 20m;
client_body_timeout 15s;
client_body_buffer_size 128k;
无限制请求体会放大上游排队和磁盘临时文件压力。修改上限会影响上传业务。
13. 控制代理缓冲策略
location /stream/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 3600s;
}
流式响应、SSE 和下载接口不应套用普通 JSON 接口的缓冲策略。长 read timeout 适用于持续有数据的流;如果上游长时间不发任何字节,仍可能被断开。
14. WebSocket 需要升级头
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
WebSocket 的存活时间还受客户端、上游和负载均衡器限制;用真实客户端验证 ping/pong。
15. upstream keepalive 配套配置
location /api/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
只写 keepalive 而不使用 HTTP/1.1 常达不到预期。连接复用可降低建连成本,但上游的最大连接、空闲超时和发布方式也必须匹配。
16. 看 Nginx 连接与文件描述符
pid=$(cat /run/nginx.pid)
cat /proc/${pid}/limits | rg 'open files'
ls /proc/${pid}/fd | wc -l
ss -s
若接近 fd 上限,先确认是长连接、日志文件、上游连接还是泄漏;提高 limit 前要同步评估 systemd 的 LimitNOFILE。
17. worker 与系统限制
worker_processes auto;
worker_rlimit_nofile 65535;
events { worker_connections 4096; }
理论连接容量还受 worker 数、上游连接、文件描述符和内核限制约束,需依据监控与压测确定。
18. 观察上游排队和应用资源
curl -s http://<上游地址>/metrics | rg 'http.*(inflight|duration|requests)|process_open_fds' | head -n 80
ps -eo pid,ppid,%cpu,%mem,etime,cmd --sort=-%cpu | head -n 20
Nginx 的 504 多半是症状,根因通常在上游。
19. 关联慢请求
awk 'match($0,/rt=([0-9.]+)/,a) && a[1] > 10 {print}' /var/log/nginx/access-upstream.log \
| tail -n 100
此 awk 依赖前文自定义日志格式。慢请求只说明时间长,根因仍须由上游 trace、SQL 慢日志或依赖指标证明。
20. 数据库依赖连接验证
mysqladmin -h <数据库地址> -u <检查账号> -p ping
mysql -h <数据库地址> -u <检查账号> -p -e "SHOW GLOBAL STATUS LIKE 'Threads_running';"
上游线程耗尽常由下游连接池等待引起。数据库查询必须使用低权限只读账号。
安全改配置:备份、灰度、验证、回滚
21. 站点配置模板
server {
listen 80;
server_name <域名>;
location /api/ {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
}
}
proxy_pass 是否保留 URI 前缀与末尾 / 有明确语义,变更前用测试 URI 验证实际转发路径。
22. 备份并替换配置
#!/usr/bin/env bash
set -euo pipefail
CONF="/etc/nginx/conf.d/app.conf"
STAMP=$(date +%F-%H%M%S)
sudo cp -a "${CONF}" "${CONF}.${STAMP}.bak"
sudo nginx -t
sudo systemctl reload nginx
curl -fsS -H 'Host: <域名>' http://127.0.0.1/<健康路径> >/dev/null
reload 会加载新配置并平滑替换 worker;若健康检查失败,立即回滚配置。
23. 回滚配置
set -euo pipefail
sudo cp -a "/etc/nginx/conf.d/app.conf.<时间戳>.bak" "/etc/nginx/conf.d/app.conf"
sudo nginx -t
sudo systemctl reload nginx
替换前确认 <时间戳> 对应本次变更,避免覆盖其他人的更新。
24. 灰度配置
sudo nginx -t && sudo systemctl reload nginx
for n in 1 2 3 4 5; do
curl -fsS -o /dev/null -w '%{http_code} %{time_total}\n' \
-H 'Host: <域名>' http://127.0.0.1/<健康路径>
done
灰度期间同时观察入口错误率、upstream 时间和上游负载。
25. 验证最终生效配置
sudo nginx -T 2>/dev/null | sed -n '/upstream app_backend/,/^}/p'
sudo nginx -T 2>/dev/null | rg -n 'proxy_(connect|send|read)_timeout'
不要以编辑器内容作为生效证据;include 顺序或重复 location 可能让预期配置被覆盖。
26. 按状态码短期统计
awk '{count[$9]++} END {for (s in count) print s, count[s]}' /var/log/nginx/access.log | sort -n
统计窗口应与变更时间对齐。状态码下降但 P99 大幅升高,仍可能是 timeout 增大后的"延迟转移"。
27. Prometheus 观察错误比例
sum(rate(nginx_http_requests_total{status=~"502|504"}[5m]))
/ clamp_min(sum(rate(nginx_http_requests_total[5m])), 1)
如果 exporter 没有 nginx_http_requests_total,先查其 /metrics,不要臆造指标名称。
28. 上游延迟分位数
histogram_quantile(0.99,
sum by (le) (rate(http_server_request_duration_seconds_bucket[5m])))
该查询依赖应用暴露 histogram;用于证明上游变慢,而不是证明 Nginx 配置错误。
29. DNS 解析问题
getent ahostsv4 <上游域名>
resolvectl query <上游域名> 2>/dev/null || true
动态域名上游需要 resolver,静态解析不会随 DNS 更新自动刷新。
resolver 10.96.0.10 valid=30s;
resolver_timeout 5s;
set $backend http://<上游域名>:<上游端口>;
proxy_pass $backend;
变量形式的 proxy_pass 会改变解析与 URI 处理方式,必须在测试环境验证。
30. 形成故障证据包
#!/usr/bin/env bash
set -euo pipefail
OUT="/var/tmp/nginx-incident-$(date +%F-%H%M%S)"
mkdir -p "${OUT}"
nginx -T > "${OUT}/nginx-effective.conf" 2>&1
ss -s > "${OUT}/socket-summary.txt"
tail -n 500 /var/log/nginx/error.log > "${OUT}/error-tail.log"
tail -n 1000 /var/log/nginx/access-upstream.log > "${OUT}/access-tail.log"
tar -C "$(dirname "${OUT}")" -czf "${OUT}.tgz" "$(basename "${OUT}")"
证据包可能含内网地址、URI 或身份信息,应限制访问并按安全流程保存。
常见误配:超时变大并不会让容量变大
当上游已经出现线程池排队时,把 proxy_read_timeout 从 30 秒改到 300 秒通常只会让更多客户端连接在 Nginx 中存活更久。并发连接上升会占用 worker、文件描述符和上游连接,最终把单个慢接口扩大为全站延迟。
应先明确接口的业务时限:同步查询、报表导出、文件上传、流式接口和异步任务不应共享同一 location 的超时策略。
31. 独立 location 隔离长任务
location = /api/report/export {
proxy_pass http://app_backend;
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 120s;
proxy_next_upstream off;
}
location /api/ {
proxy_pass http://app_backend;
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
}
导出接口若不是幂等请求,不应自动重试。修改前核对精确匹配 = 是否覆盖实际 URI。
32. 客户端、负载均衡器与上游时限一致
curl -sS -o /dev/null -w 'code=%{http_code} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
--max-time 35 -H 'Host: <域名>' http://<代理地址>/<路径>
外层超时短于 Nginx 时,客户端会先放弃;上游超时短于 Nginx 时,Nginx 只是等待一个已失败的调用。多层网关、CDN、云负载均衡器也有各自 idle timeout。
33. 上游响应头超时与响应体中断
rg -n 'upstream timed out|upstream prematurely closed connection|recv\(\) failed' \
/var/log/nginx/error.log | tail -n 200
upstream prematurely closed connection 更接近上游进程重启、崩溃或主动断连;不能一概改成 504。应同时查上游进程日志和部署事件。
34. 变更后检查连接状态
ss -tan '( sport = :80 or sport = :443 )' | awk 'NR>1 {state[$1]++} END {for (s in state) print s, state[s]}'
cat /proc/net/sockstat
TIME_WAIT、ESTAB 或 orphan 的异常增长可说明连接生命周期出现问题。状态统计要在变更前后相同时间窗比较。
35. 响应体较大接口的缓冲与磁盘风险
location /download/ {
proxy_pass http://app_backend;
proxy_buffering on;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
proxy_max_temp_file_size 1g;
}
这些参数影响内存和临时文件使用,并非性能万能开关。修改前确认 Nginx 临时目录的磁盘容量与清理策略。
关联页面
| 页面 | 关联点 |
|---|---|
| nginx-502-504-connection-reset-guide | 502/504/Connection Reset 专项排查(四段链路法) |
| nginx-troubleshooting-methodology-8-steps | Nginx 报错排查方法论(8 步决策树) |
| nginx-config-pitfalls | Nginx 典型配置错误复盘(proxy_pass 路径、keepalive 等) |
| nginx-log-analysis-troubleshooting-guide | Nginx 日志分析、4xx/5xx/超时故障排查 |
| nginx-production-performance-optimization | 生产级 Nginx 性能优化 |
| nginx-pre-launch-checklist | 上线前检查清单(超时参数等) |
| fullstack-performance-troubleshooting | 全栈性能排障入口 |