Clash API로 노드 자동 전환하는 고급 스크립트 설정
수동으로 프록시를 바꾸는 대신 Clash API 기반 헬스 체크와 장애 조치 시스템을 구성해 보세요. 지연 측정, 정책 그룹 변경, 재시도, 실패 로그 기록을 포함한 실행 가능한 자동화 예제를 제공합니다.
Clash와 mihomo에는 현재 상태를 조회하고 정책 그룹의 선택 노드를 바꿀 수 있는 외부 컨트롤러 API가 내장되어 있습니다. GUI에서 노드를 클릭하는 대신 로컬 프로그램이 여러 노드의 응답 시간과 연결 성공 여부를 확인한 뒤, 조건에 맞는 노드를 정책 그룹에 자동으로 적용하도록 만들 수 있습니다. 노드가 일시적으로 응답하지 않거나 특정 회선의 지연이 급격히 높아졌을 때 수동 조작 없이 다른 노드로 전환하는 방식입니다.
이 글에서는 Python 표준 라이브러리만 사용하는 예제를 기준으로 설명합니다. 별도의 패키지를 설치하지 않아도 되므로 Windows, macOS, Linux에서 동일한 구조로 실행할 수 있습니다. 대상 코어는 외부 컨트롤러 API를 제공하는 mihomo 계열이며, 사용하는 클라이언트의 API 주소와 인증 설정은 반드시 먼저 확인해야 합니다.
API는 기본적으로 로컬에서만 열어 두세요
외부 컨트롤러를 0.0.0.0:9090에 공개하면 같은 네트워크의 다른 기기가 정책 변경, 연결 종료, 설정 조회를 수행할 수 있습니다. API 자동화는 가능하면 127.0.0.1:9090에서만 사용하고, LAN이나 인터넷에 직접 노출하지 마세요. 외부 접근이 꼭 필요하다면 방화벽과 인증 토큰을 함께 설정해야 합니다.
Clash API 구조와 자동 전환 준비
Clash API의 핵심 설정은 external-controller와 secret입니다. 전자는 HTTP API가 수신할 주소와 포트를 지정하고, 후자는 요청 헤더에 넣는 인증 토큰입니다. GUI 클라이언트의 설정 화면에서 외부 컨트롤러를 직접 지정할 수도 있고, YAML 설정 파일에 다음과 같이 적을 수도 있습니다.
external-controller: 127.0.0.1:9090
secret: "change-this-to-a-long-random-token"
API 요청을 보낼 때 토큰이 설정되어 있다면 다음 헤더를 사용합니다. 토큰이 없는 환경에서는 헤더를 생략할 수 있지만, 자동화 스크립트가 실행되는 컴퓨터에는 인증 토큰을 설정하는 편이 안전합니다.
Authorization: Bearer change-this-to-a-long-random-token
자동 전환 대상은 일반적으로 PROXY, Proxy, 节点选择처럼 여러 노드를 포함한 select 정책 그룹입니다. 그룹 이름은 설정 파일마다 다르므로 스크립트에 추측한 이름을 하드코딩하지 말고 API 응답에서 실제 이름을 확인해야 합니다. 먼저 다음 주소를 브라우저나 curl로 조회합니다.
curl -H "Authorization: Bearer change-this-to-a-long-random-token" \
http://127.0.0.1:9090/proxies
응답의 최상위 proxies 객체에는 개별 노드와 정책 그룹이 함께 들어 있습니다. 정책 그룹은 보통 type, now, all 필드를 가지며, now는 현재 선택된 노드, all은 선택 가능한 노드 목록입니다. 이 정보를 이용하면 그룹 이름과 노드 이름에 공백이나 특수문자가 포함되어 있어도 API에서 정확하게 처리할 수 있습니다.
| API 경로 | HTTP 메서드 | 용도 |
|---|---|---|
/version | GET | 코어와 API 응답 여부 확인 |
/proxies | GET | 노드와 정책 그룹 목록 조회 |
/proxies/{name} | GET | 특정 노드 또는 그룹의 상세 상태 조회 |
/proxies/{name}/delay | GET | 지정한 URL을 이용한 지연 측정 |
/proxies/{group} | PUT | select 그룹의 현재 노드 변경 |
지연 측정과 헬스 체크 기준 만들기
단순히 가장 낮은 지연 시간만 선택하면 안정적인 자동 전환이 되지 않습니다. 지연 측정 URL이 차단된 노드, 간헐적으로 응답하는 노드, TCP 연결은 되지만 실제 HTTP 응답이 느린 노드가 모두 섞일 수 있기 때문입니다. 따라서 각 노드에 대해 제한 시간, 재시도 횟수, 허용 가능한 지연 상한을 함께 정하는 것이 좋습니다.
mihomo API의 지연 측정 요청은 다음과 같은 형태입니다. url은 측정할 HTTP 또는 HTTPS 주소이고, timeout은 밀리초 단위의 제한 시간입니다.
GET /proxies/노드이름/delay?url=https%3A%2F%2Fwww.gstatic.com%2Fgenerate_204&timeout=5000
정상적으로 측정되면 응답에는 지연 시간이 밀리초 단위로 반환됩니다. 실패하면 HTTP 오류가 발생하거나 응답을 받지 못하므로, 스크립트에서는 이를 별도의 실패 상태로 기록해야 합니다. 측정 URL은 모든 지역에서 접근 가능한 주소를 선택하고, 특정 서비스의 인증 페이지나 큰 파일을 테스트 대상으로 사용하지 않는 것이 좋습니다.
- 5초 안에 응답하지 않으면 실패로 처리합니다.
- 일시적인 패킷 손실을 고려해 같은 노드를 최대 2~3회 재시도합니다.
- 성공한 결과 중 설정한 임계값 이하인 노드만 후보로 남깁니다.
- 현재 노드가 기준을 만족하면 불필요한 전환을 하지 않습니다.
- 가장 빠른 노드와 현재 노드의 차이가 작으면 잦은 전환을 막기 위해 현재 노드를 유지합니다.
예를 들어 임계값을 800ms로 두고, 새 노드가 현재 노드보다 20% 이상 빠를 때만 전환하도록 하면 짧은 순간의 측정 오차 때문에 정책 그룹이 계속 바뀌는 현상을 줄일 수 있습니다. 네트워크 품질은 시간대에 따라 달라지므로, 30초마다 실행하는 것보다 5분 또는 10분 간격의 점검이 일반적인 데스크톱 환경에 더 적합합니다.
Python으로 노드 선택과 장애 조치 구현하기
다음 예제는 특정 정책 그룹의 후보 노드를 조회하고, 각 노드의 지연을 측정한 뒤, 가장 빠른 노드를 선택합니다. 네트워크 오류가 발생하면 재시도하고, 최종적으로 선택된 노드와 실패한 노드를 로그 파일에 기록합니다. GROUP_NAME은 자신의 설정에 표시되는 실제 정책 그룹 이름으로 바꾸세요.
import json
import logging
import time
from pathlib import Path
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
API = "http://127.0.0.1:9090"
SECRET = "change-this-to-a-long-random-token"
GROUP_NAME = "PROXY"
TEST_URL = "https://www.gstatic.com/generate_204"
TIMEOUT_MS = 5000
MAX_RETRIES = 3
MAX_DELAY_MS = 800
SWITCH_MARGIN = 0.20
logging.basicConfig(
filename=Path("clash-auto-switch.log"),
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s"
)
def api_request(path, method="GET", body=None):
data = None
headers = {"Authorization": f"Bearer {SECRET}"}
if body is not None:
data = json.dumps(body).encode("utf-8")
headers["Content-Type"] = "application/json"
request = Request(
API + path,
data=data,
headers=headers,
method=method
)
with urlopen(request, timeout=TIMEOUT_MS / 1000 + 2) as response:
return json.loads(response.read().decode("utf-8"))
def measure(node_name):
encoded_name = quote(node_name, safe="")
query = urlencode({
"url": TEST_URL,
"timeout": str(TIMEOUT_MS)
})
path = f"/proxies/{encoded_name}/delay?{query}"
for attempt in range(1, MAX_RETRIES + 1):
try:
result = api_request(path)
delay = int(result["delay"])
return delay
except (HTTPError, URLError, TimeoutError, KeyError, ValueError) as error:
logging.warning(
"health check failed node=%s attempt=%d error=%s",
node_name, attempt, error
)
if attempt < MAX_RETRIES:
time.sleep(1)
return None
def main():
payload = api_request("/proxies")
group = payload["proxies"][GROUP_NAME]
current = group.get("now")
candidates = [
name for name in group.get("all", [])
if name not in {"DIRECT", "REJECT"}
]
results = {}
for node in candidates:
delay = measure(node)
results[node] = delay
if delay is None:
logging.error("node unavailable node=%s", node)
else:
logging.info("node=%s delay=%dms", node, delay)
healthy = {
node: delay for node, delay in results.items()
if delay is not None and delay <= MAX_DELAY_MS
}
if not healthy:
logging.error("no healthy node group=%s current=%s", GROUP_NAME, current)
return
best_node = min(healthy, key=healthy.get)
best_delay = healthy[best_node]
current_delay = results.get(current)
should_switch = current_delay is None
if current_delay is not None:
should_switch = (
best_node != current and
best_delay < current_delay * (1 - SWITCH_MARGIN)
)
if should_switch:
api_request(
f"/proxies/{quote(GROUP_NAME, safe='')}",
method="PUT",
body={"name": best_node}
)
logging.info(
"switched group=%s from=%s to=%s delay=%dms",
GROUP_NAME, current, best_node, best_delay
)
else:
logging.info(
"kept group=%s current=%s delay=%s",
GROUP_NAME, current, current_delay
)
if __name__ == "__main__":
main()
이 코드에서 가장 중요한 부분은 정책 그룹 변경 요청입니다. PUT /proxies/{그룹 이름}에 {"name": "노드 이름"}을 JSON으로 보내면 select 그룹의 현재 선택 항목이 바뀝니다. 그룹 이름과 노드 이름을 URL 경로에 직접 이어 붙이지 않고 quote()로 인코딩한 이유는 한글, 공백, 슬래시, 괄호가 포함된 이름도 올바르게 처리하기 위해서입니다.
실행 전 확인할 항목
먼저 /version과 /proxies 요청이 정상 응답하는지 확인하세요. 이후 실제 그룹 이름, 노드 이름, 헬스 체크 URL을 검증한 뒤 작은 후보 목록으로 테스트하는 것이 좋습니다. API 호출이 실패해도 기존 연결을 즉시 끊지 않도록, 건강한 후보가 하나도 없을 때는 현재 설정을 유지하도록 코드를 구성해야 합니다.
재시도·장애 조치·운영 자동화
자동 전환 스크립트는 한 번 실행되는 코드보다 지속적으로 운영되는 작업에 가깝습니다. 측정 실패와 API 장애를 구분하고, 같은 노드를 반복해서 전환하지 않도록 상태와 로그를 관리해야 합니다. 예를 들어 모든 후보의 측정이 실패한 경우는 노드 전체의 문제일 수도 있지만, 로컬 인터넷 연결이나 테스트 URL 자체의 장애일 수도 있습니다. 이때 즉시 정책을 바꾸기보다는 기존 노드를 유지하고 오류만 기록하는 편이 안전합니다.
재시도 정책과 로그 기록
재시도 간격은 고정된 1초보다 짧은 지수 백오프가 유용할 수 있습니다. 첫 번째 실패 후 1초, 두 번째 실패 후 2초처럼 간격을 늘리면 일시적인 서버 응답 지연이 연속 실패로 기록되는 것을 줄일 수 있습니다. 반대로 스크립트를 1분마다 실행하면서 재시도 횟수를 지나치게 늘리면 API 요청이 많아지고 전체 점검 시간이 길어질 수 있으므로, 실행 주기와 함께 조정해야 합니다.
로그에는 최소한 실행 시각, 정책 그룹 이름, 검사한 노드, 지연 시간, 실패 원인, 전환 전후의 노드를 남기세요. 구독 링크, API 토큰, 노드 비밀번호처럼 인증 정보가 포함된 값은 로그에 기록하면 안 됩니다. 특히 API 응답 전체를 디버깅 목적으로 저장할 때는 토큰과 민감한 연결 정보가 함께 남지 않는지 확인해야 합니다.
스케줄러와 기본 정책 그룹 활용
Windows에서는 작업 스케줄러에 Python 실행 작업을 등록하고, macOS와 Linux에서는 cron 또는 systemd timer를 사용할 수 있습니다. 스크립트가 실행되는 작업 디렉터리가 달라질 수 있으므로 로그 파일은 현재 작업 폴더에 의존하지 말고 절대 경로로 지정하는 것이 좋습니다. 또한 GUI 클라이언트가 종료되어 API가 닫힌 상태에서는 실패가 정상일 수 있으므로, 종료 상태를 반복해서 경고하지 않도록 로그 수준을 조절하세요.
간단한 속도 기반 선택만 필요하다면 API 스크립트보다 mihomo의 내장 정책 그룹을 먼저 검토할 수 있습니다. url-test는 주기적으로 지연을 측정해 가장 빠른 노드를 선택하고, fallback은 지정한 URL의 연결 가능 여부에 따라 첫 번째로 정상인 노드를 사용합니다. load-balance는 여러 노드에 연결을 분산합니다. 반면 지역별 우선순위, 특정 노드 제외, 업무 시간별 전환, 실패 횟수에 따른 차단, 외부 알림 같은 조건은 별도 API 스크립트가 더 적합합니다.
proxy-groups:
- name: "자동 선택"
type: url-test
url: https://www.gstatic.com/generate_204
interval: 300
tolerance: 100
proxies:
- "JP-01"
- "SG-01"
- "US-01"
- name: "장애 조치"
type: fallback
url: https://www.gstatic.com/generate_204
interval: 300
proxies:
- "JP-01"
- "SG-01"
- "US-01"
url-test와 fallback의 동작은 코어가 관리하므로 별도 프로세스를 유지하지 않아도 된다는 장점이 있습니다. API 방식은 측정 결과를 다른 시스템과 결합하거나, 전환 조건을 세밀하게 제어할 때 사용하세요. 두 방식을 동시에 같은 그룹에 적용하면 서로 선택 상태를 덮어쓸 수 있으므로, 자동 그룹 하나에는 하나의 관리 주체만 두는 것이 안전합니다.
동작 확인과 문제 해결 순서
스크립트를 적용한 뒤에는 먼저 현재 노드가 정상인 상태에서 실행해 불필요한 전환이 발생하지 않는지 확인합니다. 그 다음 테스트용 후보의 연결을 일시적으로 차단하거나 지연 임계값을 낮춰 장애 조치 경로를 검증합니다. Clash 연결 패널에서 실제 요청이 새 노드로 나가는지 확인하고, API의 /proxies 응답에서 그룹의 now 값이 변경되었는지도 함께 확인하세요.
- 401 또는 403 오류가 발생하면
Authorization헤더의 Bearer 토큰과 클라이언트의secret을 비교합니다. - 404 오류가 발생하면 그룹 또는 노드 이름의 URL 인코딩 여부와 API 경로를 확인합니다.
- 지연 측정은 성공하지만 실제 접속이 안 되면 테스트 URL과 실제 목적지의 차이, DNS 설정, 규칙 분기를 확인합니다.
- 그룹 변경 요청은 성공하지만 화면이 바뀌지 않으면 API를 제공하는 클라이언트와 실제 트래픽을 처리하는 코어가 같은 인스턴스인지 확인합니다.
- 노드가 계속 바뀌면
SWITCH_MARGIN을 높이고 측정 주기를 늘리거나 현재 노드 유지 조건을 추가합니다. - 모든 노드가 실패하면 자동 전환을 반복하지 말고 API 상태, 로컬 방화벽, 시스템 시간, 테스트 URL의 접근 가능 여부를 점검합니다.
Clash API 자동화의 핵심은 가장 빠른 노드를 무조건 선택하는 것이 아니라, 측정 실패를 안전하게 처리하고 안정적인 상태를 오래 유지하는 데 있습니다. 먼저 mihomo의 url-test나 fallback으로 기본 장애 조치를 구성한 뒤, 기본 기능으로 처리하기 어려운 우선순위와 로그 정책이 필요할 때 Python 스크립트를 추가하면 관리 복잡도를 줄일 수 있습니다. 클라이언트 설치 파일은 다운로드 페이지에서 확인할 수 있으며, 포트와 기본 설정을 먼저 확인하려면 설정 튜토리얼을 참고하세요.
DOWNLINK READY
Clash 클라이언트 다운로드
mihomo 코어와 외부 컨트롤러 API를 지원하는 클라이언트를 플랫폼에 맞게 선택하고, 자동 전환 설정을 적용해 보세요.
DOWNLINK READY
Clash 클라이언트 다운로드
Windows, macOS, Linux, Android, iOS를 모두 지원하는 Clash 클라이언트 다운로드 페이지입니다. mihomo 코어 버전과 그래픽 인터페이스 버전을 함께 제공합니다.