这份清单用于 Linux 上由 Nginx 反向代理、systemd 托管的 Node.js 服务。命令基线为 systemd 245+、Nginx 1.18+、curl 7.68+;Node.js 版本不限,但应记录服务实际使用的二进制版本。示例约定 systemd 单元为 node-app、上游为 127.0.0.1:3000、健康检查路径为 /healthz,执行前请替换为真实值。若 Nginx 或应用位于容器中,127.0.0.1 只代表各自容器,连通性检查必须在 Nginx 所在网络命名空间内执行。
以下步骤以只读取证为主。先保留故障现场,不要一看到 502 就立即重启,否则可能丢失进程退出原因和时间关联。
0. 记录版本、时间和服务入口
date -u
node --version
nginx -v
systemctl --version | head -n 1
curl --version | head -n 1
systemctl show node-app -p MainPID -p ExecStart -p User -p WorkingDirectory -p EnvironmentFiles
含义:建立统一的 UTC 时间线,并确认 systemd 实际启动的命令、用户和工作目录。交互式终端里的 node --version 可能与 ExecStart 指向的 Node.js 不同;应以 ExecStart 中的绝对路径为准再次查询版本。
验证:MainPID 应为非零值,ExecStart 的脚本和工作目录应存在,版本信息应与部署清单一致。
1. 确认 502 确实来自目标反向代理
curl --resolve app.example.com:443:127.0.0.1 \
--silent --show-error --max-time 5 \
-D - -o /dev/null https://app.example.com/healthz
含义:绕过外部 DNS 和负载均衡,直接访问本机 443,同时保留正确的 Host 与 TLS SNI。没有 HTTPS 时改用实际的 HTTP 端口。
验证:响应状态若仍为 502,故障范围已缩小到本机 Nginx、上游连接或应用响应;若本机正常而外部入口异常,应转查负载均衡、入口网关及其健康检查。不要只凭 Server 响应头判断请求经过了哪个代理。
2. 核对 Nginx 实际生效的上游配置
sudo nginx -T 2>&1 | grep -nE 'server_name|location|proxy_pass|upstream|proxy_(connect|read|send)_timeout'
sudo nginx -t
含义:nginx -T 展开主配置及 include 文件,便于找到命中的虚拟主机、location 和 proxy_pass;nginx -t 只验证语法和引用文件,不验证上游可达。
验证:逐项确认协议、主机、端口、Unix socket 路径及目标 location。特别检查 proxy_pass 是否误写成 HTTPS、域名是否解析到旧地址,以及 URI 末尾斜杠是否改变了转发路径。若使用 upstream,所有成员都应是当前实例。
3. 检查进程状态与监听地址
systemctl is-active node-app
systemctl status node-app --no-pager -l
sudo ss -lntp | grep -E 'LISTEN.*:3000([[:space:]]|$)'
ps -fp "$(systemctl show node-app -p MainPID --value)"
含义:依次确认单元是否存活、最近一次退出原因、3000 端口由谁监听,以及 systemd 记录的主进程是否仍存在。
验证:状态应为 active,端口应处于 LISTEN,PID 应与 MainPID 对应。Nginx 转发到 127.0.0.1:3000 时,应用监听 127.0.0.1:3000 或 0.0.0.0:3000 均可;只监听 ::1:3000 无法接受发往 IPv4 回环地址的连接。容器之间不能把另一容器写成 127.0.0.1,应使用容器服务名或共享网络中的地址。
4. 绕过代理直连健康检查
curl --fail --silent --show-error --max-time 3 \
-D - -o /dev/null http://127.0.0.1:3000/healthz
printf 'exit=%s\n' "$?"
含义:直接验证 Node.js 的 TCP 建连、HTTP 解析和健康检查处理,不经过 Nginx。curl 退出码 7 常见于无法连接,28 表示超时,22 表示服务返回了 400 及以上状态。
验证:预期是退出码 0 和约定的成功状态,通常为 200。若没有 /healthz,使用一个确定存在且副作用为零的 GET 路径。若同时提供存活与就绪接口,两者都检查:存活成功但就绪失败通常指向数据库、缓存或其他依赖未就绪。
若直连成功而代理仍为 502,从 Nginx 所在主机或容器重复同一请求。结果不同通常表示网络命名空间、地址、端口、socket 权限或安全策略不一致。
5. 用同一时间窗口关联 Nginx 与应用日志
START="$(date --iso-8601=seconds)"
curl --silent --show-error --max-time 5 -o /dev/null \
https://app.example.com/healthz
sudo journalctl -u nginx -u node-app --since "$START" --no-pager -o short-iso
sudo tail -n 200 /var/log/nginx/error.log
sudo journalctl -k --since '-30 min' --no-pager | grep -iE 'out of memory|killed process'
含义:先记时间再触发一次请求,把代理错误、应用异常和内核事件放进同一时间窗口。若 Nginx 错误日志不在默认路径,应从 nginx -T 的 error_log 指令获取实际路径。
常见日志与下一步:
connect() failed (111: Connection refused):目标地址没有监听,或进程正在重启;回到步骤 2 和 3。upstream timed out:已连接但未及时响应;检查事件循环阻塞、慢依赖以及proxy_read_timeout,不要先用放大超时掩盖根因。upstream prematurely closed connection:应用在完整响应前关闭连接;查同一秒内的未捕获异常、进程退出、OOM 或请求体处理错误。no live upstreams:所有上游均被判定不可用;核对成员地址、失败计数和恢复状态。upstream sent too big header:响应头超过代理缓冲区;先定位异常膨胀的 Cookie 或响应头,再评估缓冲区配置。
如果应用没有收到对应请求日志,问题位于 Nginx 到监听端口之间;如果应用记录了请求并报错,继续沿该请求的时间戳或请求 ID 检查堆栈和下游依赖。日志中出现密钥、会话或用户数据时不要直接粘贴到公开讨论。
6. 检查健康检查配置是否与服务契约一致
确认负载均衡或编排平台使用的路径、端口、协议、Host 头、超时和成功状态范围。健康检查路径应快速、无副作用,并明确区分“进程存活”和“实例可接流量”。
可从代理侧重复验证:
for i in 1 2 3; do
curl --silent --show-error --max-time 3 -o /dev/null \
-w 'status=%{http_code} total=%{time_total}\n' \
http://127.0.0.1:3000/healthz
sleep 1
done
验证:三次请求都应返回约定的成功状态,耗时应低于健康检查超时;若状态交替变化,检查多实例上游、滚动发布状态及共享依赖。
7. 恢复后的闭环标准
只有同时满足以下条件,才可认为 502 已闭环:
- systemd 单元持续为
active,PID 与监听进程一致。 - 从 Nginx 所在网络环境直连上游连续成功。
- 经过目标虚拟主机访问同一路径连续成功。
- Nginx 错误日志和应用日志在复测窗口内没有新增对应异常。
- 外部负载均衡重新判定实例健康,真实业务路径返回预期状态。
若最终需要重启或修改配置,应先保存上述时间线和错误片段;变更后重新执行 nginx -t、直连检查与代理检查,并记录变更前后的可观察差异。