现在的 Stack Overflow 上,总有人坚信 Axios 在 HTTPS 代理上会“悄悄失灵”。这类说法在 Node.js 代理教程里反复出现,但它并不适用于本指南测试的当前版本。我搭了一个本地测试环境,把真实的 HTTP 和 HTTPS 源站分别放在两个代理后面,结果 Axios 1.19.0 对 HTTPS 请求走的是标准的 CONNECT 隧道,而不是绕过代理。
这并不代表大家提到的痛点是假的。旧版 Axios 确实存在真实 bug(可查看 issue #3384 和 issue #4531),而较新的 Node 版本又引入了另一条环境代理路径,需要更谨慎地配置。本指南会带你看清:截至今天,Axios 1.19.0 到底哪些用法是真的可用、怎样把代理接入请求、一个几乎没人讲过的拦截器轮换方案,以及当事情还是出错时,如何对照错误和修复方法一条条排查。
什么是 Axios 代理?为什么它在 Node.js 里很重要?
在 Axios 的语境里,代理就是夹在你的 Node 进程和目标网站之间的中转服务器。你的请求先发给代理,再由代理转发出去,目标站看到的是代理的 IP,而不是你的 IP。原理就这么简单。
开发者用它通常有几种原因:抓取有 IP 限流或封锁的网站、测试应用在不同地区的表现、让流量经过公司的出口网关,或者单纯不想让自己的服务器 IP 出现在某些目标站的访问日志里。Axios 官方请求配置 提供了内置的 proxy 选项,包含 host、port、protocol 和 auth 字段——这个功能已经很多年了,也是所有教程(当然也包括本文)最先会讲的内容。
但经常被忽略的一点是:proxy 这个选项在访问 HTTP 目标和 HTTPS 目标时表现并不一样,还会受到 Axios 版本的影响。也正因为这个差异,这篇指南才有存在的意义。
配置 Node.js 和 Axios(快速准备)
如果你已经有项目了,可以跳过这一段。否则,整个过程大概两分钟就能搞定。
mkdir axios-proxy-demo && cd axios-proxy-demo
npm init -y
npm install axios
如果你想用 ESM import,就在 package.json 里加上 "type": "module"(我个人会这么做——用 CommonJS 的 require() 来演示代理,多少有点旧了)。当前 Node LTS 是 v24.18.0,不过我特意用 v22.22.3 做了测试,这样结果就不会受最新运行时细节的影响。
把下面代码放进 app.js,然后执行 node app.js:
import axios from 'axios';
const res = await axios.get('https://httpbin.org/ip');
console.log(res.data);
你应该会在返回结果里看到自己的真实 IP。这就是基线——等代理配置成功后,同样的请求应该返回代理的 IP。
在启用代理之前先记下这个结果,这样你就能在下一步里把代理后的请求和基线进行对比。
Axios 真的支持 HTTPS 代理吗?先把结论说清楚
简短答案:支持,在当前稳定版里就是支持的。Axios 1.19.0 的文档明确说明了:当 HTTPS 目标站位于 HTTP 代理之后时,会通过 CONNECT 建立隧道。npm 下载 API 记录到 2026 年 7 月 31 日到 2026 年 8 月 6 日之间 Axios 的下载量为 117,890,039 次,这虽然只是一个过时的热度指标,但也说明这个库使用得非常广泛。你通过代理访问 HTTPS URL 时,当前 Axios 会发送 CONNECT 请求来建立隧道,TLS 握手则会端到端地在客户端和真实源站之间完成。我直接做了测试:本地 HTTP 代理、本地 HTTPS 源站(自签名证书),代理上的 CONNECT 计数器确实按预期增加了。
那为什么几乎所有论坛里都会出现“Axios 的 HTTPS 代理坏了”这种说法?原因有几个,而且都是真实存在的:
- 老版本 Axios。 大家常贴出来的 GitHub issue 往往已经是好多年前的内容,描述的是特定版本、特定配置下的行为,不能直接套到现在的 Axios 上。
- 代理服务器不支持 CONNECT。 在这种配置下,隧道建立会失败,Axios 应该直接报错;在判断是不是 IP 被绕过之前,先把实际路由和错误信息抓出来。
- 把
proxy配置理解错了。proxy选项是“前向代理”指令,不是一个泛用的“无论如何都帮我把所有流量走这个代理”的开关。
Chromium 在 2023 年报告称,主流平台上超过 90% 的 Chrome 导航都使用了 HTTPS。这是一项过时的 Chrome 统计,不是整个 Web 的实时普查,但它足以说明为什么 HTTPS 目标行为应该是本教程的核心。如果你还在用很老的 Axios 版本,先在当前版本线上复现问题,再判断是不是历史遗留 bug;在你自己的应用里先做升级验证,再决定是否上线。
什么时候你仍然需要显式的 Agent
原生 proxy 配置适合单个、固定、常规的代理。但一旦你需要按请求控制、代理轮换,或者 SOCKS 支持,它就不够用了——Axios 的内置选项本来就不是为这些场景设计的。这时候就轮到 HttpsProxyAgent 出场了,下面我会详细说明。可以把原生选项理解为“一个代理、一个用途,够用就行”,而基于 agent 的方案则更像“生产环境里真正想要的那种控制力”。
Node v24 和 v22.21+ 的环境代理路径
较新的 Node 版本内置了环境代理模式,可通过 NODE_USE_ENV_PROXY=1 或 --use-env-proxy 标志启用。根据 Node 官方 CLI 文档,这个能力从 v24.0.0 开始提供,并回移植到了 v22.21.0,所以说“Node 22+”并不准确,严格来说是 v22.21.0 及之后的版本。如果你还在更早的 Node 22 补丁版本里,这个标志根本不存在。
当前 Axios 已经通过它的 proxy-from-env 依赖解析 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY,所以在这条路径下并不需要 global-agent。如果同时启用了 Node 自己的环境代理模式,路由决策就可能涉及两层机制。
Axios 文档提到,在那些代理对象带有 proxyEnv 属性的 Node 版本上,Axios 会把代理解析交给 Node 自己处理,而不是走自己的逻辑。实际使用时,最好只选一种系统,并始终保持一致:
- 交给 Node 处理: 打开标志,不配置 Axios 的
proxy,让环境变量负责路由。 - 交给 Axios 处理: 不启用 Node 标志,让 Axios 自己读取环境变量。
- 完全手动控制: 明确设置
proxy: false,再提供自己的httpsAgent——这样可以完全绕开自动代理系统;一旦你需要轮换或按请求控制,我建议直接用这种方式。
我直接测试了 Axios 侧的解析:在子进程环境中设置 HTTP_PROXY 后,请求确实走了我的本地代理;再加上匹配的 NO_PROXY,下一次请求也正确地绕过了代理。也就是说,环境变量这条路现在确实开箱即用——真正需要警惕的是“双模式同时生效”的场景。
5 种把代理接入 Axios 的方法(对比)
在进入代码之前,先看看整体方案。我把下面每一种方式都接到真实的本地代理环境里做过测试,不只是看文档而已。
| 方法 | HTTPS 支持 | 支持认证 | 可按请求控制 | 适合轮换 | 复杂度 |
|---|---|---|---|---|---|
内联 proxy 选项 | ✅(当前 Axios) | ✅ | ✅ | ❌ | 低 |
axios.create() 默认配置 | ✅(当前 Axios) | ✅ | ❌(实例级) | ❌ | 低 |
环境变量(HTTP_PROXY/HTTPS_PROXY) | ✅ | ✅ | ❌ | ❌ | 低 |
httpsAgent + HttpsProxyAgent | ✅ | ✅ | ✅ | ⚠️(手动) | 中 |
| 请求拦截器 + 代理池 | ✅ | ✅ | ✅ | ✅ | 中高 |
如果你只是写一个脚本,且只需要走一个代理,就用内联选项。如果同一个模块里的所有请求都应该走同一个代理,并且你不想每次重复写配置,就用 axios.create()。如果你的基础设施团队已经在统一管理代理路由,而你只想继承环境变量,就直接用环境变量。如果原生配置不能满足你的控制需求,就上显式 agent;而当“控制”变成“轮换”时,就应该马上切到拦截器方案。

逐步操作:Axios 的基础代理配置
最简单的方式,是直接在请求里使用内置的 proxy 对象:
import axios from 'axios';
const res = await axios.get('https://httpbin.org/ip', {
proxy: {
host: '203.0.113.10',
port: 8080,
protocol: 'http',
},
});
console.log(res.data);
运行后,你应该会在响应里看到代理的 IP,而不是自己的 IP。如果你是在本地用真实代理做测试,通常不到一秒就能跑通;相比之下,光是为了测一个请求就手动去改系统级代理设置,往往能白白浪费十五分钟——而你根本不想把时间耗在这种事上。
把这个结果和前面的基线对比一下。成功的测试应该显示代理的公网 IP,而不是你之前记录的源站 IP。
使用 axios.create() 设置实例级默认值
如果某个模块里的所有请求都应该走同一个代理,那就把配置写进实例里,而不是每次都重复:
const client = axios.create({
proxy: {
host: '203.0.113.10',
port: 8080,
},
timeout: 15_000,
});
const res = await client.get('https://httpbin.org/ip');
我验证过:对单个请求设置 proxy: false,可以干净地绕开实例默认代理——这在 95% 的请求都需要代理、但少数请求(比如健康检查)不需要代理时非常有用。
通过环境变量设置代理
如果你的路由是集中管理的,比如 Docker 容器或 CI 环境里运维团队已经帮你设置好了代理变量,那你甚至不需要碰 Axios 配置:
export HTTP_PROXY=http://203.0.113.10:8080
export HTTPS_PROXY=http://203.0.113.10:8080
export NO_PROXY=localhost,127.0.0.1
当前 Axios 会读取这些变量,而且不需要 global-agent。不过别忘了前面提到的 Node 版本边界:如果 NODE_USE_ENV_PROXY 也启用了,最好明确到底谁负责路由,并在实际部署运行时验证 NO_PROXY 的效果。
逐步操作:使用 httpsAgent 配置 HTTPS 代理(适合真正需要控制的时候)
如果你的需求已经不只是“固定一个代理”,那我更推荐这一套。先安装当前的 agent 包:
npm install https-proxy-agent
https-proxy-agent 9.1.0 需要 Node 20 或更高版本,并且会先向代理发出标准的 CONNECT,再通过隧道把目标连接转发出去。
import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
const agent = new HttpsProxyAgent('http://203.0.113.10:8080');
const client = axios.create({
proxy: false, // 阻止 Axios 的原生解析再插一手
httpsAgent: agent,
timeout: 15_000,
});
const res = await client.get('https://httpbin.org/ip');
console.log(res.data);
当显式 agent 已经接管路由时,请设置 proxy: false。这样配置最清楚,也能避免 Axios 的原生代理解析或环境代理解析和你提供的 agent 发生冲突。
添加代理认证
把凭据直接放进代理 URL 里即可:
const agent = new HttpsProxyAgent('http://myuser:mypassword@203.0.113.10:8080');
如果密码里有特殊字符——@、:、# 最容易出问题——在拼接 URL 之前先做百分号编码,或者对每个部分分别用 encodeURIComponent()。密码里如果直接出现原始 @,解析器会把它当成主机名部分的开头,最后你会看到一个看似和编码无关、其实完全是编码导致的连接错误。
在 Axios 里使用 SOCKS5 代理
SOCKS 代理不能和 HttpsProxyAgent 直接兼容——这个协议需要另外的 agent:
npm install socks-proxy-agent
import { SocksProxyAgent } from 'socks-proxy-agent';
const agent = new SocksProxyAgent('socks5://myuser:mypass@203.0.113.10:1080');
const client = axios.create({
proxy: false,
httpsAgent: agent,
});
socks-proxy-agent 10.1.0 同样需要 Node 20+。当你面对的是只提供 SOCKS 网关的企业网络,或者代理服务商提供的协议能力比普通 HTTP 代理更灵活时,SOCKS5 就很值得用。
用 Axios 请求拦截器实现代理轮换
在调用代码里随机挑一个代理,做个一次性脚本还行。一旦请求量上来,比如几百次请求,这种写法就扛不住了:没人统一记录哪些代理已经挂了,没有重试逻辑,代理选择代码还会到处复制粘贴。Axios 的拦截器系统 能把这套逻辑收进一个可测试的地方;我在本文 SERP 里看过的五篇竞品教程,没有一篇用了这种方式。

构建代理池
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
class ProxyPool {
private agents: HttpsProxyAgent<string>[];
private index = 0;
constructor(proxyUrls: string[]) {
this.agents = proxyUrls.map((url) => new HttpsProxyAgent(url));
}
next(): HttpsProxyAgent<string> {
const agent = this.agents[this.index];
this.index = (this.index + 1) % this.agents.length;
return agent;
}
}
const pool = new ProxyPool([
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
]);
const client = axios.create({ timeout: 15_000 });
client.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.proxy = false;
config.httpsAgent = pool.next();
return config;
});
我把这段代码接到两个本地代理上跑过,确认请求确实会交替发送——代理 A、代理 B、然后再回到 A。注意,Axios 的请求拦截器执行顺序是后进先出,所以如果你还有别的拦截器(比如加认证头、做日志),顺序比你想象中更重要。
再加一个带重试保护的响应拦截器
这就是大多数自己写轮换脚本时最容易写乱的地方。对所有失败都无脑重试,而且还用无限代理池,可能会把一次错误请求放大成连锁问题——尤其是像 POST 这种非幂等方法,重试可能会重复触发你根本不想重复的副作用。
type RetryableConfig = InternalAxiosRequestConfig & {
__proxyRetryCount?: number;
};
client.interceptors.response.use(
undefined,
async (error: AxiosError) => {
const config = error.config as RetryableConfig | undefined;
if (!config) throw error;
const method = String(config.method ?? 'get').toUpperCase();
config.__proxyRetryCount ??= 0;
if (method !== 'GET' || config.__proxyRetryCount >= 1) throw error;
config.__proxyRetryCount += 1;
config.proxy = false;
config.httpsAgent = pool.next();
return client.request(config);
}
);
我用一个故意坏掉的代理测试过这段逻辑,确认只会在备用代理上重试一次——不会无限循环,也不会对 POST 请求重试。这才是你真正需要的边界:重试策略必须清楚知道哪些请求可以安全重放,而不是那种“先再试试,总会成功”的土办法。

错误诊断表:把每一种失败都对应到解决办法
建议你收藏这一段。这里列的都是 Axios GitHub issue 和 Stack Overflow 里真实会出现的错误,而不是假设出来的。
| 错误 / 现象 | 可能原因 | 解决方法 |
|---|---|---|
ECONNREFUSED | 主机或端口写错,或者代理服务已停止 | 先用 curl -x http://host:port target-url 验证,再去改 Axios 代码 |
407 Proxy Authentication Required | 凭据缺失或错误 | 在 proxy 配置里加入 auth: { username, password },或者把凭据直接写进 HttpsProxyAgent 的 URL |
403 Forbidden | 源站或 WAF 拒绝了请求,或者代理 IP 被拦 | 检查网站访问策略、认证和请求频率;不要把换个请求头或换个 IP 当成绕过限制的借口 |
| 返回结果里显示的是你的真实 IP | NO_PROXY、proxy:false、显式直连 agent,或者某个历史/特定版本配置正在绕过代理 | 检查到底是哪一层在负责路由;必要时用受控 IP 端点和显式 agent 验证路径 |
ETIMEDOUT | 连接或响应耗时超过了配置的超时时间 | 先测量时间到底花在哪;只有在业务确实需要时才调大超时,否则就换路由或让不健康的代理冷却一下 |
响应过程中出现 ECONNRESET | 代理、网络或源站主动断开了连接 | 记录失败发生在哪一跳;只对可安全重放的请求做有限次数重试 |
Nginx 后面的 502 Bad Gateway | Nginx 的 proxy_pass 配错了,或者 Axios 的超时和 Nginx 不一致 | 检查 proxy_connect_timeout 和 proxy_read_timeout(默认都是 60 秒),并让它们和 Axios 的 timeout 对齐 |
ERR_TLS_CERT_ALTNAME_INVALID | 目标协议用错了 agent,或者证书是自签名的 | 确认你用的是正确协议对应的 agent;本地测试时可临时设 rejectUnauthorized: false,但生产环境绝对不要这么做 |
快速排查清单
当你遇到问题又一时看不出原因时,就按这个顺序排:
- 先用
curl -x http://host:port https://your-target.com直接测试代理。如果失败,先查代理连通性、认证和目标站,再去动 Axios。如果成功,Axios 这条链路仍然需要单独验证。 - 确认你实际运行的 Axios 和 Node 版本,并在应用同样的版本和配置前,先对照历史报告。
- 搞清楚到底是谁在解析代理:原生 Axios 配置、Axios 的环境变量解析、Node 内置环境代理模式,还是显式 agent。不要让多个系统同时负责同一条请求。
- 检查
NO_PROXY,看看是不是误匹配了主机名。 - 如果你用了显式 agent,确认已经设置
proxy: false,避免 Axios 再插手处理一遍。
什么时候干脆别自己折腾代理底层
如果你的真实目标是路由任意流量——比如做公司网络测试、地理位置测试,或者控制出口流量——上面这些内容当然有用。但很多开发者之所以搜“怎么给 Axios 配代理”,其实真正想要的是网站数据,而代理只是实现手段。
如果你的情况是这样,就值得认真想一想:你到底需不需要代理,还是需要一个帮你把基础设施都处理好的 抓取 API?Thunderbit 的 Open API 只需要传入一个 URL 和 schema,就能返回结构化 JSON——不用手动解析 HTML,不用装 agent 库,也不用自己维护代理池。`/extract` 接口 会在服务端处理 JS 渲染页面、反爬机制和 CAPTCHA;如果你只需要把页面整理成干净的 Markdown,也可以用更轻量的 `/distill` 接口。它还有一个 MCP server,提供像 thunderbit_extract 和 thunderbit_suggest_fields 这样的工具,让 Claude 或 Cursor 这类编码助手在任务过程中直接拉取结构化数据,而完全不需要碰代理配置;另外还有一个 CLI,适合终端和 CI 工作流。
| 关注点 | 自己搭 Axios + 代理 | Thunderbit API / MCP / CLI |
|---|---|---|
| 代理来源与轮换 | 你自己管理 | 服务端处理 |
| 浏览器和访问挑战 | 你负责浏览器/网络层 | 由服务在其文档能力范围内代管 |
| JS 渲染页面 | 需要无头浏览器 | renderMode: full |
| 输出格式 | 原始 HTML → 你自己解析 | 通过 schema 输出结构化 JSON |
| 网站变化后的维护 | 你自己维护 | 托管式提取层能减少部分应用侧维护成本 |
说实话,如果你的目标是做测试或企业网络转发,那这些内容并不能替代 Axios 和代理配置。但如果你最终要的是结构化网页数据,API 优先的方式很可能能帮你少写很多代理、浏览器和解析代码。根据 2026 年 8 月 7 日检索时的信息,Thunderbit 的 API 限流文档 显示其免费套餐为每分钟 10 次请求、并发 2 次请求。这些都是会随时间变化的 API 限制,生产环境里务必在依赖前重新查看页面。
总结
这篇文章的核心结论,和很多老教程的说法正相反:当前 Axios 文档明确支持,而且在我们记录的本地测试中,HTTPS 目标确实走了 CONNECT 隧道。历史上的故障当然仍然重要,但必须放在对应版本和配置背景下来看。原生配置适合作为低复杂度的起点;当你需要按请求控制、SOCKS 支持或代理轮换时,配合 proxy: false 的显式 HttpsProxyAgent(或 SocksProxyAgent)能让责任边界更清楚。如果你是在生产环境里做代理池轮换,请求和响应拦截器能把逻辑集中到一个可测试的位置——但记得给重试加上循环保护,而且只重放那些真正安全重放的请求。
把上面的 诊断表 收藏起来,下次半夜 2 点代理报出一串看不懂的错误时,你会感谢现在的自己。要是你发现自己花在排查代理底层的时间,比真正使用数据的时间还多,也许该考虑一下:一个 API 优先的数据提取工具 可能比继续搭基础设施更快解决问题。
常见问题
Axios 原生支持 HTTPS 代理吗? 当前 Axios 对常规 HTTP 代理是支持的:官方文档说明了 HTTPS 目标会通过 CONNECT 隧道转发,而 Axios 1.19.0 在我们记录的本地测试中也确实走通了这条路径。历史版本和某些特定代理配置确实会出问题,所以不要想当然地认为一定成功或一定失败,关键还是要看具体版本和代理设置。
如何在 Axios 里轮换代理?
在请求发出前,用请求拦截器从代理池中挑选一个不同的 httpsAgent;再配合响应拦截器,对失败请求用另一个代理做重试。重试逻辑一定要有边界——只重试一次,而且只对像 GET 这种幂等方法重试——这样就不会误把不该重复的请求再发一遍。
为什么我的 Axios 代理显示的是真实 IP?
检查是否有 NO_PROXY、proxy:false、显式直连 agent,或者部署环境中的路由规则把代理绕过去了。记录 Axios/Node 版本,并用 cURL 独立测试代理。如果你需要毫无歧义的按请求路由,就用 HttpsProxyAgent 配合 proxy:false,再在受控端点上验证看到的 IP。
Axios 能用 SOCKS5 代理吗?
可以,通过 socks-proxy-agent 包来实现。创建一个 SocksProxyAgent 实例,把 SOCKS URL 传进去,然后作为 Axios 配置里的 httpsAgent 使用——但别同时再传 HttpsProxyAgent,因为这两种协议对应的是不同的 agent 类型。
Axios 里的 proxy 选项和 httpsAgent 有什么区别?
proxy 是 Axios 内置的单代理静态配置,适合当前版本里的简单场景。httpsAgent 则允许你接入自定义的 Node.js agent,比如 HttpsProxyAgent 或 SocksProxyAgent,让你能按请求直接控制路由、认证和轮换;这本来就不是内置选项的设计目标。


