一次 nginx 代理局域网服务 502 的排查:从 proxy_pass 到 macOS NECP

2026年06月11日2 次阅读0 人喜欢
nginxmacOSNECP反向代理网络排查

一次 nginx 代理局域网服务 502 的排查:从 proxy_pass 到 macOS NECP

背景

本机 nginx 有一个 topo-design-26683.conf,前端通过:

text 复制代码
http://localhost:26683/check-api/

代理到局域网后端:

text 复制代码
http://172.18.1.101:28880/check-api/

奇怪的是,终端直接访问后端可以正常返回 200:

bash 复制代码
curl --noproxy '*' -i http://172.18.1.101:28880/check-api/

但是经过本机 nginx 代理后一直返回 502。

初始判断:先排除 nginx 配置问题

最开始怀疑的是 locationproxy_pass 的路径写法。

原始配置里 /check-api/ 曾经代理到服务器 88 端口:

nginx 复制代码
location ^~ /check-api/ {
    proxy_pass http://172.18.1.101:88/check-api/;
    proxy_set_header Host $http_host;
}

为了减少二次代理和路径重写变量,先改成直连后端,并保留原始 URI:

nginx 复制代码
location ^~ /check-api {
    proxy_redirect off;
    proxy_pass http://172.18.1.101:28880;
    proxy_set_header Host 172.18.1.101:28880;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

nginx -t 通过,但访问仍然是 502。

关键线索:nginx 日志不是应用报错

错误日志里出现的是:

text 复制代码
connect() to 172.18.1.101:28880 failed (65: No route to host) while connecting to upstream

这说明请求没有到达后端应用,失败发生在 nginx 进程向 upstream 建立 TCP 连接阶段。

同一台机器上终端 curl 可以通,而 nginx 不通,问题范围就从“后端服务异常”缩小到“nginx 进程的网络出站行为异常”。

对比测试:不是单个后端的问题

继续测试其他局域网服务,发现 nginx 代理到多个 172.18.x.x 地址都失败,而终端直连其中一些地址是正常的。

这说明不是 check-api 单个后端的问题,而是 nginx 进程访问局域网地址整体存在限制。

系统日志确认:NECP 丢包

用 macOS 系统日志进一步验证:

bash 复制代码
/usr/bin/log show --last 20m --style compact --predicate 'eventMessage CONTAINS[c] "reason: NECP" OR eventMessage CONTAINS[c] "process: nginx"'

系统日志里出现:

text 复制代码
tcp drop outgoing ... process: nginx ... reason: NECP

这基本确认了根因:连接在 macOS 内核网络策略层被丢弃,nginx 还没真正把 SYN 包发出去。

这也解释了为什么 nginx 报 No route to host,而后端应用没有任何响应日志。

尝试过但无效的办法

1. reload nginx

只 reload 配置没有用,因为问题不是配置语法,也不是 worker 是否加载最新配置。

2. proxy_bind 绑定本机网卡 IP

曾尝试:

nginx 复制代码
proxy_bind 172.18.1.30;

但仍然被 NECP 拦截,说明不是源地址自动选择错误。

3. 给 Homebrew nginx 做 ad-hoc 签名

Homebrew 安装的 nginx 二进制最初没有签名:

text 复制代码
code object is not signed at all

尝试执行:

bash 复制代码
codesign --force --sign - /usr/local/Cellar/nginx/1.27.4/bin/nginx

签名成功后强制重启 nginx,进程 PID 也变了,但系统日志仍然显示:

text 复制代码
process: nginx ... reason: NECP

所以 ad-hoc 签名并没有解除这条网络策略。

最终绕过:使用 Host 域名访问

后来在 hosts 中增加:

text 复制代码
172.18.1.101 server-101.cn

终端验证:

bash 复制代码
curl --noproxy '*' -i http://server-101.cn:28880/check-api/

返回 200。

于是把 nginx upstream 从裸 IP 改成域名:

nginx 复制代码
location ^~ /check-api {
    proxy_redirect off;
    proxy_pass http://server-101.cn:28880;
    proxy_set_header Host server-101.cn:28880;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_connect_timeout 600;
    proxy_send_timeout 600;
    proxy_read_timeout 600;
    send_timeout 600;
    client_max_body_size 100G;
}

再次访问:

text 复制代码
http://localhost:26683/check-api/

成功返回 200。

为什么域名方式可以

server-101.cn 仍然解析到 172.18.1.101,但 macOS 的网络策略可能并不是只按最终 IP 做简单判断。

这类场景里,可能影响策略的因素包括:

  • 目标是裸 IP 还是域名解析结果
  • 连接是否经过系统 resolver 路径
  • 进程是否由 launchd 后台托管
  • 网络扩展、VPN、安全软件或本地网络权限对不同目标形式的判定

在这次现象里,裸 IP 方式会触发 NECP drop,而域名 upstream 没有触发同样拦截。

结论

这次 502 的直接原因不是 nginx proxy_pass 路径写错,也不是后端服务异常,而是 macOS 对 launchd 管理的 nginx 进程访问局域网裸 IP 的连接进行了 NECP 拦截。

最终可用方案是:

nginx 复制代码
proxy_pass http://server-101.cn:28880;
proxy_set_header Host server-101.cn:28880;

这类问题排查时,一个很有用的判断顺序是:

  1. 先用 curl --noproxy '*' 验证后端是否真的可达。
  2. 再看 nginx error log,确认是 upstream 返回错误,还是 connect 阶段失败。
  3. 如果终端能通、nginx 不能通,再查系统日志里的 NECP 或网络扩展拦截记录。
  4. 最后再考虑 Host、域名、launchd 启动方式、macOS 本地网络权限等进程级差异。

这次最终靠 Host 域名方式解决,核心不是后端认不认 Host,而是绕开了 nginx 进程直连裸局域网 IP 时被系统策略拦截的问题。

加载评论中...