DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

Nginx 代理 localhost 报 [::1] Connection refused,IPv4 后端却能访问

Nginx 日志连接的是 IPv6 回环 ::1,而后端可能只监听 127.0.0.1。通过两种地址的直连结果和监听状态确认,再局部修正上游地址并验证重载。

后端进程已经启动,直接访问 127.0.0.1 上的 HTTP 端口也正常,但经过 Nginx 仍返回 502,错误日志反复出现:

connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://[::1]:8000/"

配置里写的可能只是:

proxy_pass http://localhost:8000;

先核对日志中的 [::1]。它是 IPv6 回环地址,和 IPv4 回环地址 127.0.0.1 不同。后端只监听 IPv4 时,能访问 127.0.0.1:8000 并不能证明 ::1:8000 也有服务接收连接。

这篇文章适用于 Nginx 和 HTTP 后端处于同一网络命名空间、上游写成 localhost 的情况。Nginx 若在独立容器里,它的回环地址通常指向自己的容器,不能靠把 localhost 改成 127.0.0.1 去访问另一个容器。

把拒绝连接与应用返回错误分开

先找到与失败请求对应的新日志,读取 upstream 中的完整地址和端口。只有看到 Nginx 实际尝试连接 [::1],才有证据继续检查 IPv6 回环这一侧。

connect() failed 表示建立上游连接时出错。它与后端已经接收请求、随后返回 HTTP 500 是不同阶段。如果直接访问后端也返回 500,就还需要检查应用日志和配置;修正上游地址不保证应用内部错误随之消失。

不要只凭页面显示的 502 判断是 IPv6 问题。端口写错、后端未启动、网络隔离等情况也能导致连接失败。下面的两侧对照用于把范围缩小到具体监听地址。

在 Nginx 所在环境分别访问两种回环地址

以下以端口 8000、只读的 /health 路径为例。把它们替换成实际后端端口和一个不会改变数据的路径;如果应用依赖指定 Host,应让两次请求携带相同且正确的 Host,再比较结果。

curl --noproxy '*' --max-time 5 -v 'http://127.0.0.1:8000/health'
curl --noproxy '*' --max-time 5 -v 'http://[::1]:8000/health'

命令使用明确的 IP,避免再次由 localhost 的地址选择影响判断。--noproxy '*' 排除环境代理代发请求的干扰;-v 可以看到实际连接目标。输出可能包含请求头和响应信息,分享前应清除凭据。

若第一条建立连接并得到预期响应,第二条明确报连接被拒绝,就说明从当前环境访问这两个地址的结果不同。再核对监听端,判断是否确实只有 IPv4 监听,而不是误把不同时间或不同进程的结果放在一起比较。

Linux 上可以分别查看 IPv4、IPv6 的 TCP 监听:

ss -4 -lnt
ss -6 -lnt

在输出中找同一个端口,比较本地地址。第一份中存在 127.0.0.1:8000,第二份没有对应 IPv6 监听,与“IPv4 可连、IPv6 拒绝”相符。若显示通配监听、容器端口代理或结果不一致,应结合实际连接测试继续核对,不能只按一个地址字符串猜测双栈行为。

这些检查必须在与 Nginx 相同的网络环境中进行。宿主机上的结果不能直接替代独立容器内的结果;也不要把本机浏览器访问公网域名的 IPv6 能力,与 Nginx 到本机后端的 IPv6 连接混为一谈。

只把上游指向已经确认可用的地址

确认后端有意只提供 IPv4 回环服务后,保存当前配置副本,将相关 proxy_pass 中的主机部分改成 127.0.0.1。例如原配置为:

location / {
    proxy_pass http://localhost:8000;
}

改成:

location / {
    proxy_pass http://127.0.0.1:8000;
}

这是配置片段,放在原来的 server 中;实际配置已有的请求头、超时、WebSocket 等指令应保留。本例的 proxy_pass 没有 URI 部分。如果你原来在端口后面还有路径,保持路径和尾部斜杠不变,只替换主机地址,避免同时改变 URI 转发规则。

如果使用命名 upstream,则修改组内对应的 server localhost:8000,不要顺手更改其他节点或负载均衡参数。若业务本来要求 IPv6 回环访问,也可以按应用自身的配置方式补上监听,但需要另外验证监听范围;不应为了连接本机服务就把后端暴露到所有外部网卡。

这里只调整一个上游,无需删除系统的 IPv6 回环解析或全局禁用 IPv6。修改系统解析会影响其他程序,而这里只有一个已经确认只监听 IPv4 的上游需要明确地址。

检查实际配置并平滑重载

对常见的宿主机安装方式,可先检查配置:

sudo nginx -t

只有检查成功,才执行重载:

sudo nginx -s reload

如果运行中的 Nginx 使用指定的二进制、-c 配置路径或 -p 前缀,检查和重载必须针对同一实例。容器部署也应沿用它现有的配置检查与重载方式,不能在宿主机上检查另一个 Nginx 就当作通过。

需要确认配置文件包含关系时,可以查看 nginx -T 的输出;它会读取并展开当前文件中的配置,并不等于读取工作进程已经加载的内存配置。输出可能含有敏感配置,保留在受控位置即可。配置测试也不会替你验证上游 HTTP 接口是否正常。

重载后,再通过正式 HTTPS 入口访问刚才选定的只读路径,核对响应状态和内容,并观察对应时间段的新日志。成功标准是请求到达预期后端,且不再新增针对旧 [::1]:8000 的连接拒绝;历史日志里仍有旧错误不代表修改没有生效。

请求偶尔成功,也不能忽略失败的地址

当一个上游名称解析出多个地址时,Nginx 可以使用这些地址轮询。连接某个地址失败后,在配置和请求状态允许的条件下,也可能尝试下一个上游。因此,页面偶尔正常而日志仍有连接拒绝,并不矛盾。

不要把这种重试当成保证。重试受配置、次数、时间以及响应是否已经发给客户端等条件限制;响应传输途中断开,不能简单切换地址补齐已经发送的内容。也不要据此断言 Nginx 总会先选 IPv4,或者一次启动失败后一定永久只选 IPv6。

若修改后出现新的异常,恢复保存的配置副本,重新测试并重载,再核对服务恢复情况。恢复旧配置会带回旧的上游选择方式,只能作为回退;两种回环地址都无法连接时,应回到后端是否监听、端口和网络环境的检查,继续更换地址名称没有依据。