PAYATHON 2026

Next.js + NextAuth 中 AxiosError: ECONNREFUSED ::1:3000

数据阿明

结论

ECONNREFUSED ::1:3000 通常不是 Axios 或 NextAuth 本身的问题,而是客户端和服务端使用了不同的 IP 协议:

  • Node.js 将 localhost 解析为 IPv6 回环地址 ::1。
  • 生产模式下的 Next.js 服务只监听 IPv4 地址,或者没有监听 ::1。
  • Axios 尝试连接 ::1:3000,因此失败。
  • 127.0.0.1 明确指向 IPv4 回环地址,改用它后便能正常连接。

开发模式和 next build && next start 可能采用不同的启动方式、Node.js 版本、DNS 解析顺序或监听地址。因此,开发模式下能够通过 localhost 连接,不代表生产模式也可以。

解决这个问题,需要让请求地址和服务监听地址使用同一种协议。如果请求只发生在当前机器内部,可以明确使用 127.0.0.1。如果需要兼容 IPv6,则应让服务同时监听 IPv6。

为什么会出现 ::1

localhost 是主机名,并不固定等于 127.0.0.1。操作系统通常会为它配置两个回环地址:

127.0.0.1    localhost
::1          localhost

其中:

  • 127.0.0.1 是 IPv4 回环地址。
  • ::1 是 IPv6 回环地址。

Node.js 解析 localhost 时,可能优先返回 ::1。错误信息已经给出了实际连接目标:

address: '::1'
port: 3000
code: 'ECONNREFUSED'

这说明请求到达了本机的 IPv6 网络栈,但 ::1:3000 上没有进程接受连接。端口错误或服务尚未启动也可能产生相同错误。不过在当前案例中,127.0.0.1:3000 可以访问,因此最直接的原因是 IPv4 和 IPv6 的监听配置不一致。

方案一:内部请求明确使用 IPv4

如果应用只需要在同一台机器上调用自身接口,最简单的做法是使用 127.0.0.1:

const verifyUrl = "http://127.0.0.1:3000/api/verify";

const response = await axios.get(verifyUrl);

主机和端口不宜硬编码在业务代码中,可以将它们放入服务端环境变量:

INTERNAL_BASE_URL=http://127.0.0.1:3000
const baseUrl =
  process.env.INTERNAL_BASE_URL ?? "http://127.0.0.1:3000";

const response = await axios.get(`${baseUrl}/api/verify`);

生产环境使用的端口未必是 3000。在容器或反向代理环境中,内部地址也未必是回环地址,因此应根据部署环境配置 INTERNAL_BASE_URL。

方案二:让 Next.js 服务监听 IPv6

如果希望 localhost 解析为 ::1 时仍能正常访问,需要让服务监听 IPv6 地址。例如:

next start -H :: -p 3000

在某些操作系统中,监听 :: 也可以接受 IPv4 映射连接,但具体行为取决于操作系统及其网络配置。启动后应分别验证 IPv4 和 IPv6:

curl http://127.0.0.1:3000/api/verify
curl http://[::1]:3000/api/verify

在 URL 中使用 IPv6 字面量时,必须加方括号:

const verifyUrl = "http://[::1]:3000/api/verify";

如果只打算使用 IPv4,也可以明确监听 IPv4 地址:

next start -H 127.0.0.1 -p 3000

此时内部请求也必须使用 127.0.0.1,不能依赖 localhost 恰好被解析为 IPv4 地址。

方案三:让 Node.js 优先解析 IPv4

如果现有代码大量使用 localhost,可以在进程启动时调整 Node.js 的 DNS 结果顺序:

node --dns-result-order=ipv4first node_modules/next/dist/bin/next start

也可以通过环境变量设置:

NODE_OPTIONS=--dns-result-order=ipv4first next start

还可以在服务端入口足够早的位置设置:

import dns from "node:dns";

dns.setDefaultResultOrder("ipv4first");

这种方式会改变整个 Node.js 进程的主机名解析行为,而不只影响这一次 Axios 请求。如果问题仅涉及一个内部地址,明确配置 127.0.0.1 通常更容易理解和维护。

更合适的设计:不要通过 HTTP 调用同一进程的 API

NextAuth 的 authorize 在服务端运行,/api/verify 通常也运行在服务端。如果它们属于同一个 Next.js 应用,就没有必要为了复用验证逻辑再绕一层 HTTP。

可以将验证逻辑提取为普通的服务端函数,供 API 路由和 authorize 共同调用:

// lib/verify-credentials.js
export async function verifyCredentials(credentials) {
  // 查询数据库或调用真正的后端服务
  // 校验密码时应使用适当的密码哈希比较函数
  return user ?? null;
}

在 NextAuth 配置中直接调用:

import { verifyCredentials } from "@/lib/verify-credentials";

const credentialsProvider = {
  async authorize(credentials) {
    if (!credentials) {
      return null;
    }

    return await verifyCredentials(credentials);
  },
};

如果仍需保留 API 路由,也可以调用同一个函数:

import { verifyCredentials } from "@/lib/verify-credentials";

export default async function handler(req, res) {
  const user = await verifyCredentials(req.body);

  if (!user) {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  return res.status(200).json(user);
}

这样可以避开端口、DNS、IPv4、IPv6、反向代理和应用启动时序等问题,还能省去一次不必要的网络请求。

Axios 调用还需要修正的细节

示例中的写法:

console.log("starting fetch", axios.get(verify_url));

打印出来的是 Promise,代码并没有等待请求完成。应使用 await,同时处理异常:

try {
  const response = await axios.get(verifyUrl);
  console.log("verification response", response.data);
  return response.data;
} catch (error) {
  if (axios.isAxiosError(error)) {
    console.error("verification request failed", {
      code: error.code,
      address: error.cause?.address,
      port: error.cause?.port,
      message: error.message,
    });
  }

  return null;
}

如果验证接口接收用户名和密码,通常应使用 POST,以免将敏感信息放进查询字符串:

const response = await axios.post(
  `${baseUrl}/api/verify`,
  {
    username: credentials.username,
    password: credentials.password,
  },
  {
    timeout: 5000,
  }
);

注意事项

  • 浏览器能访问 http://localhost:3000,不代表服务端 Node.js 进程一定会选择相同的 IP 地址。
  • 不要通过修改系统 hosts 文件、删除 ::1 localhost 来掩盖问题,这可能影响其他程序。
  • 在 Docker 中,127.0.0.1 指向当前容器,而不是宿主机或其他容器。跨容器请求应使用服务名。
  • 在无服务器部署中,不能假设本机始终有一个 Next.js 进程监听 3000 端口。
  • 对外地址和内部地址应分别配置。公开站点 URL 未必适合作为服务器内部的回环请求地址。
  • 如果必须通过 HTTP 调用自身接口,应等应用开始监听端口后再发送请求,并设置合理的超时时间。
  • 同一应用内的认证逻辑应优先调用共享的服务端函数。只有接口属于独立服务时,才更适合通过 Axios 请求。

备注:内容仅供参考。