Clash 的 external-controller 不只是 Dashboard 的连接入口,它本质上是一套可以被脚本调用的 HTTP 控制接口。通过这套 API,程序能够读取策略组状态、测试节点延迟、切换当前节点,甚至在发现连续故障后执行自动恢复。对于节点数量较多、网络质量变化明显,或者需要在服务器上长期运行 Clash 的用户来说,将人工点击切换升级为自动化运维,可以显著降低断网时间。本文以 Clash/Mihomo 为基础,介绍 external-controller 的安全配置、节点健康检查、API 调用方式,以及一套可以按需改造的自动切换脚本。
external-controller 的工作原理与安全边界
Clash 启动后会同时运行代理服务和控制服务。代理服务负责接收浏览器、系统或 TUN 转发过来的网络请求;控制服务则监听一个单独的 HTTP 地址,供 Dashboard 或自动化程序访问。两者使用同一个 Clash 进程,但职责完全不同。控制服务可以读取当前配置、查询连接、获取策略组信息,也可以修改策略组正在使用的节点。
在配置文件中,最基本的启用方式如下。建议优先绑定本机地址,只有确实需要远程管理时才开放局域网访问。
# 仅允许本机访问,适合桌面端脚本 external-controller: 127.0.0.1:9090 # API 认证密钥,生产环境不要留空 secret: replace-with-a-long-random-secret # 如需局域网管理,可改为 0.0.0.0,但必须配合防火墙 # external-controller: 0.0.0.0:9090
当配置了 secret 后,调用 API 时必须在 HTTP 请求头中加入 Authorization: Bearer 密钥。如果将监听地址设置为 0.0.0.0 而没有设置密钥,局域网中的其他设备就可能直接切换节点、读取代理信息,甚至影响正在进行的连接。即使设置了密钥,也不建议把 9090 端口暴露到公网。
- 桌面端本机脚本:使用
127.0.0.1:9090,通过本地回环接口访问。 - 路由器或服务器:绑定内网地址,使用防火墙限制来源 IP,只允许管理设备访问。
- 跨公网管理:优先通过 WireGuard、Tailscale 或 SSH 隧道进入内网,不要直接开放控制端口。
- 密钥管理:不要把 secret 写入公开仓库,脚本中应使用环境变量或单独的权限文件。
核心 API 与节点健康检查方法
自动切换的关键不是简单地“发现延迟高就换节点”,而是先确定要监控哪个策略组,再使用稳定的测试地址判断节点是否可用。Mihomo/Clash 常用的几个接口如下:
| 用途 | 请求方式 | 接口 | 说明 |
|---|---|---|---|
| 读取所有策略组 | GET | /proxies | 返回策略组、当前节点和节点列表 |
| 读取单个策略组 | GET | /proxies/{名称} | 查看某个组当前选择和可用成员 |
| 测试节点延迟 | GET | /proxies/{名称}/delay | 需要传入 url 与 timeout 参数 |
| 切换策略组 | PUT | /proxies/{名称} | 请求体为 JSON 格式的 proxy 名称 |
| 读取当前连接 | GET | /connections | 辅助判断是否存在活跃连接和异常流量 |
例如,查看名为“自动选择”的策略组,可以执行以下请求。策略组名称需要进行 URL 编码,包含中文、空格或特殊字符时尤其要注意。
# 使用 curl 查询策略组 curl -s \ -H "Authorization: Bearer ${CLASH_SECRET}" \ "http://127.0.0.1:9090/proxies/$(python3 -c 'import urllib.parse; print(urllib.parse.quote("自动选择"))')"
节点延迟测试通常通过 /proxies/{proxy}/delay 完成,并传入一个返回 204 或 200 的稳定 URL。测试 URL 不宜使用体积很大的网页,否则测到的结果会混入下载时间;也不建议只使用一个容易被本地网络缓存的地址。可以选择轻量级的 HTTPS 探测地址,并将超时时间设置在 3000 至 8000 毫秒之间。
# 节点名称为“节点-HK-01”时,先进行 URL 编码 curl -s \ -H "Authorization: Bearer ${CLASH_SECRET}" \ "http://127.0.0.1:9090/proxies/%E8%8A%82%E7%82%B9-HK-01/delay?url=https%3A%2F%2Fwww.gstatic.com%2Fgenerate_204&timeout=5000"
健康检查应该至少考虑三个结果:请求成功且延迟合理、请求超时或连接失败、请求成功但延迟明显高于阈值。不要因为一次超时就立刻切换,否则短暂丢包会造成策略组频繁抖动。更稳妥的做法是保存连续失败次数,例如连续两次或三次失败后才将节点标记为不健康;节点恢复时也可以连续成功两次后再恢复使用。
通过 HTTP API 执行自动切换
切换策略组使用 PUT 请求,JSON 请求体中的字段名是 name。需要注意,切换的是策略组当前选择的代理,而不是直接修改 YAML 文件,因此切换通常立即生效,但是否在 Clash 重启后保留,取决于客户端的配置持久化行为。若用户手动选择了节点,自动脚本也可能覆盖这个选择,因此建议专门建立一个“自动故障转移”策略组。
# 将“自动选择”切换到“节点-JP-01” curl -X PUT \ -H "Authorization: Bearer ${CLASH_SECRET}" \ -H "Content-Type: application/json" \ -d '{"name":"节点-JP-01"}' \ "http://127.0.0.1:9090/proxies/%E8%87%AA%E5%8A%A8%E9%80%89%E6%8B%A9"
下面是一份 Python 示例,它会读取指定策略组中的节点,逐一进行延迟测试,并在当前节点连续失败时切换到第一个可用节点。示例使用标准库,不依赖第三方包,适合放在桌面电脑、Linux 服务器或路由器环境中运行。实际使用时应根据节点数量、网络质量和 API 版本调整参数。
import json
import os
import time
import urllib.parse
import urllib.request
import urllib.error
API = os.getenv("CLASH_API", "http://127.0.0.1:9090")
SECRET = os.environ["CLASH_SECRET"]
GROUP = "自动选择"
TEST_URL = "https://www.gstatic.com/generate_204"
TIMEOUT = 5000
FAIL_LIMIT = 2
headers = {
"Authorization": "Bearer " + SECRET,
"Content-Type": "application/json",
}
def request(path, method="GET", data=None):
body = None if data is None else json.dumps(data).encode("utf-8")
req = urllib.request.Request(
API + path, headers=headers, method=method, data=body
)
with urllib.request.urlopen(req, timeout=8) as response:
return json.loads(response.read().decode("utf-8"))
def quote(value):
return urllib.parse.quote(value, safe="")
def test_proxy(proxy):
path = "/proxies/" + quote(proxy) + "/delay?url=" + quote(TEST_URL)
path += "&timeout=" + str(TIMEOUT)
try:
result = request(path)
return int(result.get("delay", 0))
except (urllib.error.URLError, ValueError, TimeoutError):
return None
def switch_to(proxy):
request("/proxies/" + quote(GROUP), "PUT", {"name": proxy})
print("switched to:", proxy)
def main():
group = request("/proxies/" + quote(GROUP))
current = group.get("now")
candidates = group.get("all", [])
failures = 0
while True:
delay = test_proxy(current)
if delay is None:
failures += 1
print("unavailable:", current, "failure:", failures)
else:
failures = 0
print("healthy:", current, "delay:", delay, "ms")
if failures >= FAIL_LIMIT:
for candidate in candidates:
if candidate == current:
continue
candidate_delay = test_proxy(candidate)
if candidate_delay is not None:
switch_to(candidate)
current = candidate
failures = 0
break
time.sleep(60)
if __name__ == "__main__":
main()
脚本中的 all 字段由策略组类型和 Clash 内核版本决定,某些版本也可能返回 proxies 或其他结构。首次运行时,建议先打印完整的 API 返回值,确认策略组名称、当前节点字段和候选节点字段,再进行自动化改造。不要直接假设所有客户端的 JSON 结构完全相同,尤其是 Clash for Windows、Clash Verge Rev 与 Mihomo 在功能支持上可能存在差异。
生产环境中的稳定性、日志与排错
自动切换系统最常见的问题不是 API 不会调用,而是判断条件过于简单。一个节点的延迟升高,并不一定代表它已经不可用;测试地址被临时阻断,也不一定说明整条线路故障。因此,健康检查应区分“可用性”和“性能”两个维度。前者决定是否允许继续使用,后者只用于在多个可用节点之间择优。
- 设置失败阈值:建议连续失败 2 至 3 次再切换,避免单次丢包触发故障转移。
- 设置恢复阈值:备用节点连续成功若干次后,再考虑切回主节点,避免主备之间来回震荡。
- 增加冷却时间:切换后至少等待 60 秒再进行下一次主动切换,给 TCP、TLS 和 DNS 连接留下恢复时间。
- 保留当前节点:如果当前节点仍然可用,即使另一个节点延迟更低,也不必立即切换,可以设置 50 至 100 毫秒的延迟差阈值。
- 记录切换原因:日志中写入时间、当前节点、测试结果、目标节点和失败次数,便于区分线路故障与脚本误判。
如果 API 返回 401 Unauthorized,通常是 secret 不正确或 Bearer 格式遗漏;返回 404,可能是接口路径、策略组名称或内核版本不匹配;返回连接拒绝,则应检查 Clash 是否启动、监听端口是否正确,以及脚本是否运行在另一台设备上。中文策略组名称导致的编码问题也很常见,必须使用 URL 编码后的名称,不能直接把空格或中文拼接进路径。
如果请求可以成功,但测试结果始终超时,应先在运行 Clash 的同一台机器上执行 curl,确认测试 URL 可以访问。某些节点只支持 TCP 或只支持 UDP,某些网络还会拦截特定探测域名;此时可以准备两个或三个不同的测试地址,并要求至少一个地址成功后才判定节点可用。测试地址最好与实际业务类型接近,例如主要访问网页就使用 HTTPS 轻量请求,主要运行游戏或实时通信则不能只依赖网页延迟。
# Linux/macOS export CLASH_API="http://127.0.0.1:9090" export CLASH_SECRET="replace-with-your-secret" python3 auto_switch.py # Windows PowerShell $env:CLASH_API = "http://127.0.0.1:9090" $env:CLASH_SECRET = "replace-with-your-secret" python .\auto_switch.py
对于普通桌面用户,Clash 内置的 fallback 或 url-test 策略组通常已经足够,不必额外运行脚本。API 自动切换更适合需要自定义规则的场景,例如按业务选择测试地址、在多个策略组之间联动、发送故障通知,或在远程服务器上执行无人值守运维。部署前先用手动 curl 验证接口,再逐步加入日志、失败阈值和切换冷却机制,能够避免脚本错误影响全部网络流量。
当你需要将节点健康检查、故障转移和运行状态监控整合到一套稳定流程中时,Clash 的 external-controller 提供了足够灵活的基础。只要控制好 API 暴露范围,选择合理的探测策略,并对自动切换设置明确的边界,就能在不频繁手动操作的情况下,让代理连接更具连续性和可维护性。