Clash API节点自动切换:外部控制器进阶配置指南
为什么要用 external-controller 做节点自动切换? 详解
很多用户第一次接触 Clash API 节点自动切换,通常是因为发现客户端里的 url-test、fallback 或手动切换还不够灵活:节点测速结果可能只反映某个测试网址,策略组也不会主动通知你的脚本;当某条线路出现高丢包、连接超时或特定服务不可用时,仍然需要手动打开 Clash Verge、Clash Verge Rev 或 Mihomo 的界面修改节点。对于长时间运行的开发机、家庭网关、远程服务器和下载任务来说,这种处理方式既慢,也无法留下清晰的故障记录。
external-controller 是 Mihomo、Clash Meta 及部分 Clash 客户端提供的本地 HTTP 控制接口。它让外部程序能够读取当前配置、查看代理组、执行延迟测试、切换代理,以及查询连接和内核状态。简单理解,Clash 客户端负责运行代理内核,API 则提供一扇可编程的控制门。你可以用 Shell、Python、Node.js 或监控系统定时访问这扇门,按照延迟、HTTP 状态码、连续失败次数和节点黑名单自动做出切换决定。
本文聚焦一个可以实际落地的方案:先确认控制器监听地址与鉴权方式,再读取目标代理组,逐个测试候选节点,最后通过 API 把健康节点写回策略组。文章中的接口名称以 Mihomo 兼容实现为主,不同客户端的菜单名称可能略有差异,但只要客户端提供 external-controller,基本都能按照相同思路排查。本文不建议把控制器暴露到公网,而是默认脚本与 Clash 运行在同一台设备上。
先明确边界:API 自动切换只能帮助你在已有节点中选择更合适的出口,不能修复订阅过期、节点账户失效、规则写错或上游服务本身拒绝访问的问题。自动化之前,先确保客户端手动选择每个候选节点时都能正常联网。
开启控制器并做好鉴权保护 全面解析
在配置文件中,控制器通常写在顶层,而不是写进 proxy-groups 或 rules。一个适合本机脚本调用的基础配置如下:
external-controller: 127.0.0.1:9090
secret: "change-this-to-a-long-random-token"
127.0.0.1:9090 表示只接受本机访问,脚本可以通过 http://127.0.0.1:9090 调用。若写成 0.0.0.0:9090,局域网内其他设备也可能尝试连接;除非你明确需要远程管理,否则不建议这样配置。控制器端口不是代理端口,不能把它填到浏览器或系统代理设置中。常见的 HTTP 混合端口可能是 7890,而 API 控制端口常见为 9090,两者用途完全不同。
secret 是 API 访问令牌。启用后,所有请求都应在 HTTP Header 中带上 Authorization: Bearer 令牌。不要把令牌直接写进公开仓库、聊天记录或网页 JavaScript,也不要为了调试长期关闭鉴权。更稳妥的做法是把令牌放在只有当前用户可读的环境变量中:
export CLASH_API_SECRET='replace-with-your-secret'
export CLASH_API='http://127.0.0.1:9090'
如果你使用 Clash Verge Rev 或其他图形客户端,修改配置后需要确认实际运行的配置已经重新加载。有些客户端会在订阅刷新时重新生成配置,直接编辑缓存文件可能很快被覆盖。建议把控制器字段放进客户端支持的覆写、Merge 或本地配置区域,并在控制器页面确认当前监听端口。首次测试可以执行:
curl -fsS \
-H "Authorization: Bearer ${CLASH_API_SECRET}" \
"${CLASH_API}/version"
能够返回版本信息,说明地址、端口和鉴权基本正确。若返回 401,优先检查令牌是否多了空格或引号;若是 Connection refused,检查内核是否启动、端口是否写错;若请求一直等待,则要排查客户端是否监听在 IPv6、控制器是否被防火墙拦截,以及脚本访问的地址是否与界面显示一致。
安全提醒:不要把 external-controller 直接映射到公网,也不要把带有完整 secret 的 curl 命令复制到工单或日志系统。控制器通常可以切换节点、读取配置并查看连接,泄露后影响远大于普通网页端口。
读取代理组、测试节点并执行切换 一次讲透
自动切换的关键不是盲目轮询所有节点,而是先找到一个明确的选择型代理组。假设配置里有一个名为「自动选择」的组,组内包含多个真实节点。API 路径中的组名需要进行 URL 编码,中文名称、空格和特殊字符不能直接拼接。为了减少脚本复杂度,建议将自动化目标命名为不含空格的英文或短中文名称,例如 Auto 或 Proxy。
查看全部代理组可以请求:
curl -fsS \
-H "Authorization: Bearer ${CLASH_API_SECRET}" \
"${CLASH_API}/proxies"
返回的 JSON 中,proxies 对象包含各个代理组和节点。选择型代理组通常有 type: "Selector",并带有 all、now 等字段。now 是当前正在使用的成员,all 是可以切换的候选列表。不要把 DIRECT、REJECT、另一个策略组或测试专用组误当成真实节点;脚本应维护排除列表,并根据返回数据动态过滤。
对单个节点执行延迟测试时,可以使用代理延迟接口。示例中的 URL 只是测试目标,实际项目应选择稳定、响应内容简单且符合你所在网络环境的地址:
curl -fsS -G \
-H "Authorization: Bearer ${CLASH_API_SECRET}" \
--data-urlencode "url=https://www.gstatic.com/generate_204" \
--data-urlencode "timeout=5000" \
--data-urlencode "接点名称=${NODE}" \
"${CLASH_API}/proxies/${NODE}/delay"
上面的示例为了强调 URL 编码,节点名称字段在实际调用中应使用接口要求的参数名 proxy,完整写法如下:
curl -fsS -G \
-H "Authorization: Bearer ${CLASH_API_SECRET}" \
--data-urlencode "url=https://www.gstatic.com/generate_204" \
--data-urlencode "timeout=5000" \
--data-urlencode "proxy=${NODE}" \
"${CLASH_API}/proxies/${NODE}/delay"
不同 Mihomo 版本对路径中的节点名和查询参数处理可能存在细微差异。如果返回 404 或提示参数错误,不要立刻判断 API 不支持,先查看客户端 Dashboard 使用的请求格式,或者访问控制器的接口文档确认版本。部分实现允许将代理名称放在路径中,部分实现更依赖 URL 编码;中文节点名尤其容易因为未编码而失败。
延迟最低并不总是最佳节点。一次测速可能刚好命中缓存、短暂拥塞也可能在下一分钟恢复。因此脚本至少应记录多次结果,并设置合理阈值:
- 超时阈值:普通网页场景可以使用 3000 至 5000 毫秒;跨地区链路或移动网络不宜设置得过低。
- 连续失败:建议连续两至三次失败才触发切换,避免一次丢包导致频繁抖动。
- 最小改善幅度:新节点至少比当前节点低 80 至 150 毫秒,或当前节点连续失败,才执行切换。
- 冷却时间:切换后数分钟内不要再次自动切换,避免长连接、下载任务和登录会话被反复打断。
- 黑名单:记录最近失败的节点,并设置过期时间,不要让故障节点在下一轮立即重新入选。
选出节点后,通过代理组的 PUT 接口切换。组名和节点名必须作为 JSON 字符串传递:
curl -fsS -X PUT \
-H "Authorization: Bearer ${CLASH_API_SECRET}" \
-H "Content-Type: application/json" \
--data "{\"name\":\"${NODE}\"}" \
"${CLASH_API}/proxies/Auto"
执行切换后,不要只看 HTTP 返回码。再次读取 /proxies/Auto,确认 now 已经变成目标节点;随后访问一个实际业务网址,确认规则确实命中这个组。API 返回成功只代表控制器接受了请求,不代表目标节点已经完成 TLS 握手,也不代表某个特定站点一定可用。
构建一个可审计的 Shell 自动切换脚本 深入理解
下面给出一个精简的自动化骨架。它的重点不是提供一份适用于所有环境的万能脚本,而是展示安全的调用顺序:读取代理组、筛选候选节点、逐个测速、保存最佳结果、执行一次切换。生产环境中应根据你的 JSON 解析工具和客户端版本补充错误处理。
#!/usr/bin/env bash
set -Eeuo pipefail
API="${CLASH_API:-http://127.0.0.1:9090}"
SECRET="${CLASH_API_SECRET:?CLASH_API_SECRET is required}"
GROUP="${CLASH_GROUP:-Auto}"
TEST_URL="${CLASH_TEST_URL:-https://www.gstatic.com/generate_204}"
TIMEOUT="${CLASH_TIMEOUT:-5000}"
api_get() {
curl -fsS --max-time 8 \
-H "Authorization: Bearer ${SECRET}" "$1"
}
api_put() {
curl -fsS --max-time 8 -X PUT \
-H "Authorization: Bearer ${SECRET}" \
-H "Content-Type: application/json" \
--data "$2" "$1"
}
group_json="$(api_get "${API}/proxies/${GROUP}")"
current="$(printf '%s' "$group_json" | jq -r '.now')"
best=""
best_delay=999999
while IFS= read -r node; do
[ -z "$node" ] && continue
case "$node" in
DIRECT|REJECT|GLOBAL|URL-TEST|Fallback) continue ;;
esac
result="$(curl -fsS -G --max-time 8 \
-H "Authorization: Bearer ${SECRET}" \
--data-urlencode "url=${TEST_URL}" \
--data-urlencode "timeout=${TIMEOUT}" \
--data-urlencode "proxy=${node}" \
"${API}/proxies/$(printf '%s' "$node" | jq -sRr @uri)/delay" \
|| true)"
delay="$(printf '%s' "$result" | jq -r '.delay // empty' 2>/dev/null || true)"
if [[ "$delay" =~ ^[0-9]+$ ]] && [ "$delay" -lt "$best_delay" ]; then
best="$node"
best_delay="$delay"
fi
done < <(printf '%s' "$group_json" | jq -r '.all[]')
if [ -n "$best" ] && [ "$best" != "$current" ]; then
api_put "${API}/proxies/$(printf '%s' "$GROUP" | jq -sRr @uri)" \
"$(jq -n --arg name "$best" '{name: $name}')"
printf '%s switched %s -> %s (%sms)\n' \
"$(date -Is)" "$current" "$best" "$best_delay"
else
printf '%s keep %s; no better healthy node\n' "$(date -Is)" "$current"
fi
这份脚本使用 jq 解析 JSON,并通过环境变量读取敏感信息。运行前先手动确认组名:
export CLASH_GROUP='Auto'
export CLASH_API_SECRET='your-secret'
chmod 700 ./clash-auto-switch.sh
./clash-auto-switch.sh
如果计划通过 cron 定时执行,不建议每分钟运行一次。可以根据节点数量和网络稳定性设置五到十五分钟的间隔,并把标准输出写入受保护的日志文件。日志应包含时间、当前节点、候选节点、延迟、失败原因和是否发生切换,但不要输出完整订阅链接、API 令牌或代理服务器密码。对于下载、视频会议和 SSH 等长连接场景,建议增加「工作时间不切换」或「当前连接数超过阈值不切换」的条件。
还可以把自动切换分成两个层级。第一层由策略组自身的 url-test 或 fallback 负责快速健康检查,第二层由外部脚本负责业务验证,例如访问公司代码仓库、包管理器注册表或指定 API。这样做比让脚本单独承担所有测速更稳:内核负责基础可用性,脚本只在真正需要时修正策略。若你的节点很多,也可以先用内核策略组缩小候选范围,再让 API 测试其中几个节点,降低控制器请求量。
自动切换失效时,按链路逐层排查 详解
排查 API 自动切换,不要一上来就反复更换节点。整个链路可以拆成「控制器可访问、接口能鉴权、代理组可识别、延迟测试成功、切换结果生效、业务流量命中」六个环节,任何一环失败都会表现成脚本没反应。
| 现象 | 常见原因 | 处理方法 |
|---|---|---|
| 连接被拒绝 | 内核未启动、端口错误或监听地址不一致 | 检查客户端当前配置,执行 curl /version,确认端口正在监听。 |
| 返回 401 | secret 不匹配、Bearer 拼写错误或环境变量为空 | 重新复制令牌,使用 printf 检查是否带入换行和多余空格。 |
| 代理组返回 404 | 组名未编码、名称大小写不同或目标不是选择型组 | 先请求 /proxies,从 JSON 中复制准确的组名。 |
| 所有节点都是超时 | 测试网址不可达、参数格式不兼容或节点本身失效 | 换一个测试 URL,并在客户端界面逐个手动测试节点。 |
| API 显示已切换但网页不变 | 规则没有命中该组、系统代理未开启或应用使用 TUN 之外的链路 | 查看连接日志、规则命中情况和系统代理状态,不要只看策略组名称。 |
| 节点频繁来回切换 | 阈值过低、没有冷却时间或测速目标抖动 | 增加连续失败次数、最小改善幅度和切换冷却时间。 |
如果脚本在终端运行正常,放进 cron 后却失败,通常是环境变量、工作目录或权限问题。cron 不一定加载交互式 Shell 的配置文件,因此应在任务中使用绝对路径,明确设置 PATH,并在脚本开头读取一个权限为 600 的环境文件。Linux 上可用 systemd timer 替代 cron,方便配置启动顺序、失败重试和日志查看;Windows 用户则可以使用任务计划程序,以当前用户权限运行 PowerShell 或 WSL 脚本。
如果控制器运行在 Docker、虚拟机或 WSL 中,还要额外检查网络命名空间。容器内的 127.0.0.1 只代表容器自身,不一定能访问宿主机上的 Clash。此时应通过明确的宿主机地址、合适的端口映射或同一网络模式连接,但仍要限制控制器访问范围。WSL 场景尤其容易出现「Windows 客户端界面能用,Linux 脚本访问不到 9090」的情况,这并不一定是 API 失效,而可能是监听地址和虚拟网络边界造成的。
最后,自动切换并不等于自动解决所有网络问题。节点延迟低但 DNS 污染、规则集过期、TLS 指纹被限制、上游 API 返回 403 时,继续换节点只会制造更多噪声。建议保留一次切换前后的连接日志,并将「网络失败」「鉴权失败」「业务拒绝」分开统计。只有当脚本具备可解释的日志和冷却机制,它才是可靠的运维工具,而不是一个不停点击切换按钮的循环程序。
相比之下,市面上一些只提供图形化测速的同类工具,在外部控制器鉴权、节点切换审计和跨平台脚本接入方面往往配置繁琐,遇到中文组名、容器网络或多次失败重试时也缺少清晰提示;单纯依赖客户端内置自动测速,又很难加入业务网址验证与自定义冷却规则。Clash 更适合把 Mihomo API、Shell 自动化、日志记录和故障排查放在同一套可理解的流程里,既保留图形界面的直观操作,也方便进阶用户按本文方法扩展自己的健康检查逻辑。如果你希望先把客户端和控制器环境准备好,不妨前往下载,再从本机 API 测试开始逐步搭建自动切换方案。