nginx 转发超时故障排查实录

一、故障现象

生产环境系统调用测试平台的接口(https://10.0.0.10:38088/order/...,其中 10.0.0.10 是内网 nginx 代理,负责转发到 https://api.example-test.com)全部失败,无一成功。

应用日志的表现非常诡异------每次调用都打三行:

java 复制代码
ERROR OkHttpUtils timeout
INFO  OkHttpUtils post 接口调用成功》》》https://10.0.0.10:38088/order/...
ERROR NullPointerException: Cannot invoke "okhttp3.Response.close()" because "response" is null

二、分层定位:用 curl 的四个时间点拆链路

运维反馈:部署 jar 的服务器 ping 10.0.0.10 是通的。

但 ping 只证明 ICMP 通,不代表 TCP 端口能建立连接,更不代表应用层有响应。用 curl 的计时参数做分层测试:

bash 复制代码
curl -vk --connect-timeout 5 --max-time 60 https://10.0.0.10:38088/order/ \
  -w '连接:%{time_connect}s TLS:%{time_appconnect}s 首字节:%{time_starttransfer}s 总:%{time_total}s'

结果:

text 复制代码
连接耗时:0.001521s  TLS握手完成:0.032000s  首字节:0.000000s  总耗时:60.000850s
curl: (28) Operation timed out after 60000 milliseconds with 0 bytes received
  • TCP 连接 1.5ms,TLS 握手 32ms ------ 到代理的链路飞快
  • 首字节永远为 0,60 秒零字节 ------ 代理收到请求后没有任何下文

正常服务就算路径不对也会立刻回 404/401,一个字节都不回,说明代理收到请求后卡死在某个环节。

同时在代理服务器本机直连目标服务验证:

bash 复制代码
curl -vk --max-time 15 https://api.example-test.com/
# 秒回 HTTP/1.1 404(根路径未配置 API,属正常响应)

代理 → 测试平台的出网、DNS、对端服务全部正常。问题锁定在代理软件自身的转发环节

四、第二重陷阱:nginx 499

查看代理的 access log,清一色的 499:

java 复制代码
10.0.0.50 - - [24/Aug/2026:10:13:17 +0800] "POST /order/list HTTP/1.1" 499 0 "-" "okhttp/4.9.1"

499 是 nginx 特有状态码,含义是"客户端在 nginx 返回响应之前主动断开了连接"。

处理过程还原:

  1. OkHttp 请求到达 nginx(日志确认收到);
  2. nginx 开始转发给上游测试平台,一直等不到上游响应;
  3. OkHttp 等满 10 秒超时,主动断开 → nginx 记一条 499。

卡点确认在 nginx → 测试平台的转发环节。但本机 curl 测试平台又是秒回的------差别必然在 nginx 自己使用的解析/转发路径上。

五、水落石出:error log 一行破案

查看 nginx error.log:

java 复制代码
2026/08/24 08:22:09 [error] connect() failed (110: Connection timed out)
while connecting to upstream, ...,
upstream: "https://198.51.100.10:443/gateway/lark/open/oauth2/auth?..."

nginx 实际转发到了 198.51.100.10------但配置里写的明明是域名:

nginx 复制代码
location /order/ {
    proxy_pass https://api.example-test.com/;
    proxy_ssl_server_name on;
    proxy_set_header Host api.example-test.com;
    ...
}

坑在这里:proxy_pass 写静态域名时,nginx 只在启动/reload 时解析一次 DNS(走系统 resolver),之后一直使用当时缓存的 IP,直到下次 reload/restart。

api.example-test.com 的 DNS 解析是:

text 复制代码
api.example-test.com → CNAME → gtm-test.gtm-example.net → 198.51.100.20

测试平台走 GTM 全局流量调度,IP 会随时切换。nginx 启动时解析到的 198.51.100.10 在 GTM 切换后失效,nginx 还抱着旧 IP 不放,SYN 发出去石沉大海------connect timeout,永久挂起。

强制用旧 IP 验证,实锤:

bash 复制代码
# 旧 IP:超时
curl -vk --resolve api.example-test.com:443:198.51.100.10 https://api.example-test.com/ --max-time 10
# curl: (28) Connection timed out after 10001 milliseconds
新 IP(正常 DNS 解析):正常响应
curl -vk --resolve api.example-test.com:443:198.51.100.20 https://api.example-test.com/ --max-time 10
HTTP/1.1 404(秒回)

故障时间线也吻合:error log 最早 08:22,应用日志最早 08:17 开始超时。

六、修复

应急恢复(无需改配置):

bash 复制代码
nginx -s reload

reload 会让 nginx 重新解析域名,拿到当前有效 IP,业务立即恢复。

根治:改成变量形式的 proxy_pass,让 nginx 在请求时按 resolver 动态解析。

注意一个坑:原配置 proxy_pass https://api.example-test.com/; 末尾的 / 作用是把 /order/ 前缀剥掉(error log 中上游路径为 /gateway/... 可证实),而变量形式的 proxy_pass 不允许带 URI ,需要用 rewrite ... break 替代:

nginx 复制代码
location /order/ {
    rewrite ^/order/(.*)$ /$1 break;
    resolver 10.0.0.53 valid=60s ipv6=off;   # 内网 DNS
    set $order_upstream https://api.example-test.com;
    proxy_pass $order_upstream;
    proxy_ssl_server_name on;
    proxy_set_header Host api.example-test.com;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

改动点:

  • rewrite ^/order/(.*)$ /$1 break; 替代原 proxy_pass 末尾 / 的剥前缀作用;
  • proxy_pass 改为变量(不带任何路径),配合 resolver 实现运行时解析;
  • valid=60s 控制解析缓存,GTM 切换后最多 1 分钟自动生效;
  • proxy_ssl_server_name on 必须保留,否则回源握手不带 SNI,CDN 会拒绝。

生效并验证:

bash 复制代码
nginx -t && nginx -s reload
curl -vk --max-time 15 https://10.0.0.10:38088/order/list

能拿到任何 HTTP 响应(哪怕 404)即恢复。

七、教训总结

  1. 误导性日志比没有日志更糟糕。 "接口调用成功"四个字让排查先走了弯路;catch 里 close 不判空产生的 NPE 又把真实异常彻底埋掉。写工具类时:资源释放判空、日志区分成败、异常如实抛出。
  2. "ping 通"≠ 网络通。 用 curl 的 time_connect / time_appconnect / time_starttransfer 四个时间点,可以快速定位卡在 TCP、TLS 还是应用层。
  3. nginx 499 = 客户端先放弃了。 排查方向应该往上游查,而不是怀疑客户端。
  4. proxy_pass 写域名 ≠ 动态解析。 上游是 GTM/CDN 类调度域名时,必须 resolver + 变量形式 proxy_pass,否则对端换 IP 必死。
  5. 整条链路每个环节看起来都"正常"(TCP 通、TLS 通、DNS 通、对端服务正常),只有把它们分段拆开逐一对比,才能找到坏掉的那一段。
相关推荐
2401_8653825044 分钟前
信息化建设项目申报建设费时运维费能否一起报送?
运维
九硕智慧建筑一体化厂家1 小时前
大型建筑集群智慧管控升级!IBMS系统实现多系统一体化融合管理
运维·人工智能·笔记·智慧城市
HAHAXX81 小时前
低代码 & 无代码 RPA 项目实施:对接大模型接口的部署与运维要点
运维·低代码·rpa
ITyunwei09871 小时前
从 ITIL 视角量化-第三集:工单系统如何成为 ITSM 治理抓手?架构+量化
运维·网络·企业微信
paopao_djshddhdj1 小时前
钉钉培训系统功能与收费详解:企业数字化培训的优选方案
运维·钉钉
肠畔码农2 小时前
Redis 深度内核解析与高性能运维调优指南
运维·数据库·redis
迪康coolmu2 小时前
当大模型遇上终端安全—AI驱动的智能运维实践
运维·人工智能·安全
遇见小修修2 小时前
黄埔附近维修口碑推荐榜
运维
丰锋ff3 小时前
初识Linux操作系统
linux·运维·服务器