Clash API节点自行切换:外部控制器进阶配置指南

为什么要用 external-controller 做节点自行切换? 内在逻辑与实践

大批用户第一次接触 Clash API 节点自行切换,按常规是因为发现客户端里的 url-test、fallback 或逐项切换还不够活:节点测速结果存在存在存在可能只反映某个测试网址,策略组也不会主动通知你的脚本;

external-controller 是 Mihomo、Clash Meta 及部分 Clash 客户端提供的本地 HTTP 控制接口。它让外部程序能够读取当前配置、查看代理组、执行耗时测试、切换代理,以及查询连接和内核状态。

朴素理解,Clash 客户端负责跑代理内核,API 则提供一扇可编程的控制门。你完全能用 Shell、Python、Node.js 或监控系统定时开这扇门,按照耗时、HTTP 状态码、连续弹出错漏次数和节点黑名单自行做出切换决定。

下文聚焦一个完全能实际落地的方案:先确认控制器监听地址与鉴权方式,再读取目标代理组,逐个测试候选节点,最后通过 API 把健康节点写回策略组。文章中的接口名称以 Mihomo 兼容实现为主,不同客户端的菜单名称存在存在存在可能略有差异,但只要客户端提供 external-controller,基本都能按照相同思路排查。下文不更推荐把控制器暴露到公网,而是初始脚本与 Clash 跑在同一台设备上。

从机制上看:当某条线路出现高丢包、连接超时或特定服务不可用时,仍然免不了要逐项开 Clash Verge、Clash Verge Rev 或 Mihomo 的人机人机人机界面修改节点。对于长时间跑的开发机、家庭网关、远程服务器和下载任务来说,这种处理方式既慢,也无法留下看得明白的故障记录。

先明确边界:API 自行切换只能帮助你在已有节点中拿定更合适的出口,不能修复订阅过期、节点账户失效、规则写错或上游服务本身拒绝开的要留意之处。自行化之前,先确保客户端逐项拿定每个候选节点时都能正常联网。

开启控制器并做好鉴权保护 技术拆解

在配置文件中,控制器按常规写在顶层,而不是写进 proxy-groups 或 rules。一个适合本机脚本调用的基础配置如下:

external-controller: 127.0.0.1:9090
secret: "change-this-to-a-long-random-token"

secret 是 API 开令牌。启用后,所有这些这些这些请求都应在 HTTP Header 中带上 Authorization: Bearer 令牌。不要把令牌一上来写进公开仓库、聊天记录或网页 JavaScript,也不要为了调试长期关闭鉴权。更稳妥的做法是把令牌放在只有当前用户可读的环境变量中:

如果你上手上手上手使用 Clash Verge Rev 或其他图形客户端,修改配置后免不了要确认实际跑的配置已经重新加载。有些客户端会在订阅刷新时重新生成配置,一上来编辑缓存文件存在存在存在可能很快被覆盖。更推荐把控制器字段放进客户端能跑的覆写、Merge 或本地配置区域,并在控制器页面确认当前监听端口。首次测试完全能执行:

export CLASH_API_SECRET='replace-with-your-secret'
export CLASH_API='http://127.0.0.1:9090'

更严格地说:能够返回版本信息,说明地址、端口和鉴权基本到位。若返回 401,在前验一遍令牌是否多了空格或引号;若是 Connection refused,验一遍内核是否唤起、端口是否写错;若请求一直等待,则要排查客户端是否监听在 IPv6、控制器是否被防火墙拦截,以及脚本开的地址是否与人机人机人机界面显示一致。

curl -fsS \
  -H "Authorization: Bearer ${CLASH_API_SECRET}" \
  "${CLASH_API}/version"

127.0.0.1:9090 表示只接受本机开,脚本完全能通过 http://127.0.0.1:9090 调用。若写成 0.0.0.0:9090,局域网内其他设备也存在存在存在可能尝试连接;除非你明确免不了要远程管理,否则不更推荐这样配置。控制器端口不是代理端口,不能把它填到浏览器或系统代理设置中。司空见惯的 HTTP 混合端口存在存在存在可能是 7890,而 API 控制端口司空见惯为 9090,两者用途完全不同。

安全方面提醒:不要把 external-controller 一上来映射到公网,也不要把带有一应俱全 secret 的 curl 命令复制到工单或日志系统。控制器按常规完全能切换节点、读取配置并查看连接,泄露后影响远大于普通网页端口。

读取代理组、测试节点并执行切换 结构解析

自行切换的关键不是盲目轮询所有这些这些这些节点,而是先找到一个明确的拿定型代理组。假设配置里有一个名为「自行拿定」的组,组内包含多个真实节点。API 路径中的组名免不了要进行 URL 编码,中文名称、空格和特殊字符不能一上来拼接。为了减少脚本复杂度,更推荐将自行化目标命名为不含空格的英文或短中文名称,打个比方 Auto 或 Proxy。

返回的 JSON 中,proxies 对象包含各个代理组和节点。拿定型代理组按常规有 type: "Selector",并带有 all、now 等字段。now 是当前正在上手上手上手使用的成员,all 是完全能切换的候选列表。不要把 DIRECT、REJECT、另一个策略组或测试专用组误当成真实节点;脚本应维护排除列表,并根据返回数据动态过滤。

curl -fsS \
  -H "Authorization: Bearer ${CLASH_API_SECRET}" \
  "${CLASH_API}/proxies"

换个层面:对单个节点执行耗时测试时,完全能上手上手上手使用代理耗时接口。示例中的 URL 只是测试目标,实际项目应拿定经得起折腾、响应信息朴素且符合你所在网络环境的地址:

上面的示例为了强调 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 "接点名称=${NODE}" \
  "${CLASH_API}/proxies/${NODE}/delay"

不同 Mihomo 版本对路径中的节点名和查询参数处理存在存在存在可能存在细微差异。如果返回 404 或提示参数错漏,不要立刻判断 API 不能跑,先查看客户端 Dashboard 上手上手上手使用的请求格式,或者开控制器的接口文档确认版本。部分实现允许将代理名称放在路径中,部分实现更依赖 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 "proxy=${NODE}" \
  "${CLASH_API}/proxies/${NODE}/delay"

往细处想:耗时最低并不总是最佳节点。一次测速存在存在存在可能刚好命中缓存、短暂拥塞也存在存在存在可能在下一分钟恢复。正因如此脚本至少应记录多次结果,并设置合理阈值:

超时阈值:普通网页当下完全能上手上手使用 3000 至 5000 毫秒;跨地区链路或移动网络不宜设置得过低;连续弹出错误:更推荐连续两至三次弹出错误才触发切换,避免一次丢包导致频繁抖动;最小改善幅度:新节点至少比当前节点低 80 至 150 毫秒,或当前节点连续弹出错误,才执行切换;冷却时间:切换后数分钟内不要再次自行切换,避免长连接、下载任务和登录会话被反复打断;黑名单:记录最近弹出错误的节点,并设置过期时间,不要让故障节点在下一轮立即重新入选。

选出节点后,通过代理组的 PUT 接口切换。组名和节点名必须作为 JSON 字符串传递:

先厘清一件事:执行切换后,不要只看 HTTP 返回码。再次读取 /proxies/Auto,确认 now 已经变成目标节点;随后开一个实际业务网址,确认规则确实命中这个组。API 返回成功只代表控制器接受了请求,不代表目标节点已经完成 TLS 握手,也不代表某个特定站点一定可用。

curl -fsS -X PUT \
  -H "Authorization: Bearer ${CLASH_API_SECRET}" \
  -H "Content-Type: application/json" \
  --data "{\"name\":\"${NODE}\"}" \
  "${CLASH_API}/proxies/Auto"

查看全部代理组完全能请求:

构建一个可审计的 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

如果计划通过 cron 定时执行,不更推荐每分钟跑一次。完全能根据节点数量和网络经得起折腾性设置五到十五分钟的间隔,并把标准输出写入受保护的日志文件。日志应包含时间、当前节点、候选节点、耗时、弹出错误由来和是否发生切换,但不要输出一应俱全订阅链接、API 令牌或代理服务器密码。对于下载、视频会议和 SSH 等长连接当下,更推荐增加「工作时间不切换」或「当前连接数超出阈值不切换」的条件。

export CLASH_GROUP='Auto'
export CLASH_API_SECRET='your-secret'
chmod 700 ./clash-auto-switch.sh
./clash-auto-switch.sh

从机制上看:还完全能把自行切换分成两个层级。第一层由策略组自身的 url-test 或 fallback 负责很快健康验一遍,第二层由外部脚本负责业务验证,打个比方开公司代码仓库、包管理器注册表或指定 API。这样做比让脚本单独承担所有这些这些测速更稳:内核负责基础可用性,脚本只在真正免不了要时修正策略。若你的节点很多,也完全能先用内核策略组缩小候选范围,再让 API 测试其中几个节点,往下压控制器请求量。

这份脚本上手上手使用 jq 解析 JSON,并通过环境变量读取敏感信息。跑前先逐项确认组名:

自行切换失效时,按链路逐层排查 内在逻辑与实践

排查 API 自行切换,不要一上来就反复更换节点。通盘链路完全能拆成「控制器可开、接口能鉴权、代理组可识别、耗时测试成功、切换结果生效、业务流量命中」六个环节,每一种一环弹出错误都会表现成脚本没反应。

现象 司空见惯由来 处理手段
连接被拒绝 内核未唤起、端口错漏或监听地址不一致 验一遍客户端当前配置,执行 curl /version,确认端口正在监听。
返回 401 secret 不匹配、Bearer 拼写错漏或环境变量为空 重新复制令牌,上手使用 printf 验一遍是否带入换行和多余空格。
代理组返回 404 组名未编码、名称大小写不同或目标不是拿定型组 先请求 /proxies,从 JSON 中复制准确的组名。
所有这些节点都是超时 测试网址不可达、参数格式不兼容或节点本身失效 换一个测试 URL,并在客户端人机界面逐个逐项测试节点。
API 显示已切换但网页不变 规则没有命中该组、系统代理未开启或应用上手使用 TUN 之外的链路 查看连接日志、规则命中实际表现和系统代理状态,不要只看策略组名称。
节点频繁来回切换 阈值过低、没有冷却时间或测速目标抖动 增加连续报错次数、最小改善幅度和切换冷却时间。

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 测试开始逐步搭建自行切换方案。

如果脚本在终端跑正常,放进 cron 后却弹出错误,按常规是环境变量、工作目录或权限要留意之处。cron 不一定加载交互式 Shell 的配置文件,正因如此应在任务中上手上手使用绝对路径,明确设置 PATH,并在脚本开头读取一个权限为 600 的环境文件。