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 通、对端服务正常),只有把它们分段拆开逐一对比,才能找到坏掉的那一段。
相关推荐
前端世界27 分钟前
Linux服务器实战:NTP时间同步、SELinux权限与rsyslog日志管理,一次搞懂三大运维问题
linux·运维·服务器
Android系统攻城狮28 分钟前
Linux Gstreamer深度解析之gst_audio_converter_new调用流程与实战(二十)
linux·运维·服务器·gstreamer音视频·音视频进阶
海宇服务39 分钟前
零信任架构实战:基于海宇对外投资历史查询服务构建自动化供应商准入网关
运维·人工智能·架构·自动化
上海云盾-小余1 小时前
BGP 高防底层原理:TCP 异常流量识别与清洗机制
运维·网络·tcp/ip
言乐62 小时前
Python加速器4跨境网络加速器
运维·服务器·开发语言·网络·python
蓝速科技5 小时前
医院导诊 AI 数字人一体机场景适配与落地指南丨蓝速科技
运维·数据库·人工智能·科技·自然语言处理·技术分享
H_oRIZoN_6 小时前
Linux入门DAY41(51 单片机 串口与通信协议)
linux·运维·单片机
小马同学-7 小时前
OpenStack 使用实战:Web 界面与 CLI 命令行实验
运维·云计算·openstack
网安老伯8 小时前
都2026年了,还在问网络安全怎么入门?看完这一篇你就懂了
运维·计算机网络·web安全·网络安全·wireshark·密码学·网络攻击模型
小祺先生9 小时前
mt7921 debian系统 如何安装驱动
运维·网络·debian