Clash API로 노드 자동 전환하기: 지연 감시와 고급 스크립트

노드 장애나 응답 지연이 발생했을 때 Clash 프록시 그룹을 자동으로 변경하는 구성법을 익혀보세요. 개발자와 네트워크 엔지니어를 위해 상태 점검, API 호출, 예약 실행, 장애 분석 절차를 실제 예제와 함께 정리했습니다.

Clash의 프록시 그룹은 화면에서 직접 노드를 선택하지 않아도 외부 컨트롤러 API를 통해 변경할 수 있습니다. 따라서 특정 노드의 응답 지연이 임계값을 넘거나 연결 테스트가 실패했을 때, 스크립트가 다른 노드를 선택하도록 만들 수 있습니다. 이 방식은 mihomo를 사용하는 Clash Verge Rev, 일부 데스크톱 클라이언트, 서버에서 직접 실행하는 mihomo에 적용할 수 있지만, 클라이언트가 external-controller를 노출하고 있어야 합니다.

다만 노드 자동 전환은 단순히 지연 시간이 가장 낮은 노드를 고르는 기능이 아닙니다. 측정 URL의 위치, DNS 해석, 프록시 그룹의 종류, API 인증, 현재 선택된 정책 이름이 모두 결과에 영향을 줍니다. 이 글에서는 먼저 API를 안전하게 열고, 현재 그룹과 노드 상태를 확인한 뒤, 지연 감시 스크립트와 예약 실행을 단계적으로 구성합니다.

external-controller API 준비와 보안 범위

mihomo의 REST API는 보통 127.0.0.1:9090에서 수신하도록 설정합니다. 로컬 스크립트만 사용할 목적이라면 모든 인터페이스에 바인딩하는 0.0.0.0:9090을 피하는 것이 좋습니다. 외부에 열어야 하는 특별한 이유가 없다면 루프백 주소에 고정하고, 반드시 긴 secret을 지정하세요.

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

설정 파일을 수정한 뒤에는 클라이언트를 재시작하거나 설정을 다시 로드해야 합니다. API 요청에는 다음처럼 Authorization: Bearer 헤더를 넣습니다. secret이 비어 있는 환경도 있지만, 자동화 스크립트가 실행되는 컴퓨터에는 다른 로컬 프로세스가 있을 수 있으므로 인증을 생략하지 않는 편이 안전합니다.

API="http://127.0.0.1:9090"
SECRET="change-this-to-a-long-random-secret"

curl -fsS \
  -H "Authorization: Bearer ${SECRET}" \
  "${API}/version"

/version 요청이 JSON으로 응답하면 컨트롤러 주소와 인증 헤더가 정상적으로 동작하는 것입니다. 응답이 401 Unauthorized라면 secret이 다르거나 헤더 형식이 잘못된 것이고, 연결 거부가 나오면 포트가 열리지 않았거나 클라이언트가 실행 중이 아닌 상태입니다.

API 포트를 인터넷에 직접 공개하지 마세요

외부 컨트롤러 API는 노드 목록 조회뿐 아니라 정책 그룹 변경, 설정 갱신, 연결 종료 같은 작업도 수행할 수 있습니다. 방화벽으로 9090 포트를 차단하고, 원격 관리가 필요하면 SSH 터널이나 인증된 내부 네트워크를 사용하세요. 스크립트 파일과 셸 히스토리에 secret을 그대로 남기는 것도 피하는 것이 좋습니다.

자동 전환에 사용하는 주요 엔드포인트

자동 전환에는 모든 API를 사용할 필요가 없습니다. /proxies는 전체 프록시와 그룹을 조회하고, /proxies/{name}은 특정 그룹의 현재 선택 상태와 후보 목록을 보여줍니다. 그룹의 선택 노드를 바꾸는 요청은 PUT /proxies/{name}이며, 본문에 선택할 프록시 이름을 넣습니다.

목적 메서드와 경로 주의할 점
API 동작 확인 GET /version 인증과 포트부터 확인
그룹 목록 확인 GET /proxies 그룹 이름과 노드 이름을 구분
그룹 상세 확인 GET /proxies/{name} URL 인코딩이 필요한 이름이 있음
노드 지연 측정 GET /proxies/{name}/delay urltimeout 쿼리 필요
선택 노드 변경 PUT /proxies/{name} JSON 본문은 {"name":"노드명"}

프록시 그룹과 노드 상태 조회

자동화 전에 먼저 사용하려는 그룹의 실제 이름을 확인해야 합니다. 설정 파일에 적은 YAML 이름과 API 응답의 이름은 대소문자와 공백을 포함해 정확히 일치해야 합니다. 예를 들어 그룹 이름이 🌐 자동 선택이라면 URL 경로에 그대로 넣지 말고 URL 인코딩해야 합니다.

curl -fsS \
  -H "Authorization: Bearer ${SECRET}" \
  "${API}/proxies" | jq '.proxies | keys'

특정 그룹의 상세 정보는 다음과 같이 확인할 수 있습니다. 아래 예시의 Auto를 실제 그룹 이름으로 바꾸세요.

GROUP="Auto"

curl -fsS --get \
  -H "Authorization: Bearer ${SECRET}" \
  --data-urlencode "name=${GROUP}" \
  "${API}/proxies/${GROUP}" | jq .

일부 환경에서는 경로의 그룹 이름을 자동으로 인코딩하지 않으므로, 셸에서 GROUP을 직접 경로에 붙이는 방식보다 Python의 urllib.parse.quote나 curl의 URL 인코딩 옵션을 사용하는 편이 안전합니다. 응답에는 대체로 현재 선택된 now, 그룹 유형을 나타내는 type, 후보 목록인 all이 포함됩니다.

select, url-test, fallback 그룹의 차이

select 그룹은 사용자가 선택한 노드를 유지하므로 외부 스크립트가 PUT 요청을 보내야 바뀝니다. 반면 url-test는 커널이 설정된 URL을 주기적으로 검사해 가장 적합한 노드를 선택할 수 있고, fallback은 앞쪽 후보가 실패했을 때 다음 후보로 넘어갑니다. 이미 목적에 맞는 내장 그룹을 사용하고 있다면 별도 스크립트가 상태를 계속 덮어쓰지 않도록 해야 합니다.

proxy-groups:
  - name: Auto
    type: url-test
    use:
      - provider-main
    url: "https://www.gstatic.com/generate_204"
    interval: 300
    tolerance: 80

  - name: Manual
    type: select
    proxies:
      - Auto
      - DIRECT

url-testinterval은 초 단위이며, tolerance는 현재 선택된 노드와 새 후보의 지연 차이가 이 값보다 클 때 교체하는 기준입니다. 자동 스크립트는 내장 기능보다 세밀한 조건, 예를 들어 연속 세 번 실패했을 때만 전환하거나 특정 노드를 제외할 때 유용합니다. 두 방식을 같은 그룹에 동시에 적용하면 내장 검사와 스크립트가 서로 다른 노드를 선택할 수 있으므로 역할을 분리하세요.

지연 감시와 노드 자동 전환 스크립트

다음 Bash 예시는 지정한 select 그룹의 후보를 하나씩 검사하고, 응답에 성공한 노드 중 지연 시간이 가장 낮은 노드를 선택합니다. jq가 필요하며, Linux와 macOS의 기본 셸에서 실행할 수 있습니다. 측정 URL은 실제 사용할 서비스와 가까운 HTTPS 엔드포인트로 바꾸되, 응답 본문 전체를 다운로드하지 않는 204 또는 작은 정적 파일을 선택하세요.

#!/usr/bin/env bash
set -Eeuo pipefail

API="${CLASH_API:-http://127.0.0.1:9090}"
SECRET="${CLASH_SECRET:?CLASH_SECRET is required}"
GROUP="${CLASH_GROUP:-Manual}"
TEST_URL="${CLASH_TEST_URL:-https://www.gstatic.com/generate_204}"
TIMEOUT="${CLASH_TIMEOUT:-5000}"
MAX_MS="${CLASH_MAX_MS:-1800}"

auth=(-H "Authorization: Bearer ${SECRET}" -H "Content-Type: application/json")

json="$(curl -fsS "${auth[@]}" "${API}/proxies")"
encoded_group="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${GROUP}")"

current="$(printf '%s' "${json}" | jq -r --arg g "${GROUP}" '.proxies[$g].now // empty')"
mapfile -t candidates < <(printf '%s' "${json}" |
  jq -r --arg g "${GROUP}" '.proxies[$g].all[]?')

best=""
best_ms=999999

for node in "${candidates[@]}"; do
  encoded_node="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${node}")"

  result="$(curl -fsS --get "${auth[@]}" \
    --data-urlencode "url=${TEST_URL}" \
    --data-urlencode "timeout=${TIMEOUT}" \
    "${API}/proxies/${encoded_node}/delay" 2>/dev/null || true)"

  delay="$(printf '%s' "${result}" | jq -r '.delay // empty' 2>/dev/null || true)"

  if [[ "${delay}" =~ ^[0-9]+$ ]] && (( delay < best_ms )); then
    best="${node}"
    best_ms="${delay}"
  fi
done

if [[ -z "${best}" ]]; then
  echo "No healthy proxy found" >&2
  exit 2
fi

if (( best_ms > MAX_MS )); then
  echo "Best proxy is ${best_ms} ms, threshold is ${MAX_MS} ms" >&2
  exit 3
fi

if [[ "${best}" != "${current}" ]]; then
  body="$(jq -nc --arg name "${best}" '{name: $name}')"
  curl -fsS -X PUT "${auth[@]}" \
    --data "${body}" \
    "${API}/proxies/${encoded_group}" > /dev/null
  echo "Switched ${GROUP}: ${current:-none} -> ${best} (${best_ms} ms)"
else
  echo "Keep ${current} (${best_ms} ms)"
fi

이 스크립트는 후보 목록을 그룹의 all 필드에서 얻습니다. 그룹 안에 다른 그룹 이름이나 DIRECT가 들어 있으면 그것도 후보로 반환될 수 있으므로, 실제 운영 환경에서는 all 목록을 확인해 측정 가능한 프록시만 남기는 것이 좋습니다. 또한 노드 이름에 특수문자가 포함될 수 있으므로 경로에 그대로 연결하지 않고 URL 인코딩합니다.

실행할 때는 secret을 환경 변수로 전달합니다. 파일 안에 직접 적지 않으면 권한이 제한된 서비스 계정으로 스크립트를 운영하기 쉽습니다.

chmod 700 switch-node.sh

export CLASH_SECRET='change-this-to-a-long-random-secret'
export CLASH_GROUP='Manual'
export CLASH_TEST_URL='https://www.gstatic.com/generate_204'
export CLASH_MAX_MS=1500

./switch-node.sh
전환 성공과 인터넷 사용 가능 여부는 별개입니다

/delay 요청이 성공했다는 것은 해당 프록시를 통해 테스트 URL에 접근했다는 뜻입니다. 브라우저가 사용하는 규칙, DNS 모드, UDP 지원, 실제 목적지의 차단 정책까지 검증한 것은 아닙니다. 전환 후에는 API의 현재 선택 값과 실제 클라이언트 트래픽을 따로 확인해야 합니다.

연속 실패와 히스테리시스로 빈번한 전환 방지

한 번의 측정 실패만으로 노드를 바꾸면 일시적인 패킷 손실이나 테스트 URL의 순간적인 지연에도 선택 값이 계속 바뀝니다. 운영 환경에서는 같은 노드가 두세 번 연속 실패했을 때만 전환하거나, 마지막 전환 후 일정 시간 동안은 다시 바꾸지 않는 히스테리시스를 추가하는 편이 안정적입니다.

  • 단일 실패는 기록만 하고 현재 노드를 유지합니다.
  • 연속 3회 실패하면 후보 전체를 측정합니다.
  • 새 노드가 현재 노드보다 최소 100~200ms 빠를 때만 교체합니다.
  • 전환 후 60초 동안은 재전환하지 않아 연결 재수립이 반복되지 않게 합니다.
  • 모든 후보가 실패하면 현재 선택 값을 유지하고, DIRECT로 자동 변경하지 않습니다.

예약 실행, 로그 설계와 장애 분석

스크립트가 수동 실행에서 정상적으로 동작한 뒤에만 예약 실행으로 옮기세요. Linux에서는 cron이나 systemd timer를 사용할 수 있습니다. 아래 cron 예시는 5분마다 실행하며, 표준 출력과 오류를 별도 로그에 남깁니다.

*/5 * * * * CLASH_SECRET='change-this-to-a-long-random-secret' \
  CLASH_GROUP='Manual' \
  /opt/clash/switch-node.sh >> /var/log/clash-switch.log 2>&1

secret이 프로세스 목록이나 cron 설정에 노출되는 것이 걱정된다면 chmod 600으로 제한한 환경 파일을 읽도록 바꾸세요. systemd를 사용할 때는 전용 사용자와 EnvironmentFile을 지정하고, 로그에는 secret과 전체 API 응답을 기록하지 않는 것이 좋습니다. 노드 이름과 지연 시간, 전환 전후의 그룹 이름 정도면 대부분의 장애 분석에 충분합니다.

Windows에서는 작업 스케줄러에서 프로그램으로 powershell.exe 또는 WSL의 Bash를 등록하고 5분 간격 트리거를 설정할 수 있습니다. Clash Verge Rev 같은 GUI 클라이언트가 로그인 이후에만 실행된다면 예약 작업의 시작 시점을 사용자 로그인 후로 맞추세요. API가 아직 열리지 않은 부팅 초기에 실행하면 실패하므로, 작업 시작 후 30~60초 지연을 두는 것도 도움이 됩니다.

응답 코드와 로그를 기준으로 원인 분리하기

증상 가능성 높은 원인 확인할 항목
연결 거부 컨트롤러 미실행, 포트 불일치 클라이언트 설정의 external-controller, 로컬 포트
401 Unauthorized secret 또는 Bearer 헤더 오류 환경 변수 값, 앞뒤 공백, 재시작 여부
404 Not Found 그룹 이름 인코딩 오류 또는 잘못된 경로 /proxies의 실제 키와 URL 인코딩
200이지만 후보가 없음 그룹이 비어 있거나 provider 갱신 실패 all, proxy-providers, 구독 상태
지연 측정 전부 실패 노드 장애, DNS 문제, 테스트 URL 차단 다른 테스트 URL과 클라이언트 로그

API에서 그룹 선택 값이 바뀌었는데 트래픽이 그대로라면 시스템 프록시가 다른 포트를 사용하거나, TUN이 다른 클라이언트의 코어를 통과하고 있을 가능성이 있습니다. 반대로 API 전환 자체가 실패한다면 브라우저나 DNS를 먼저 의심하지 말고 그룹 이름, 인증 헤더, 컨트롤러 로그를 확인하세요.

마지막으로 자동 전환 스크립트는 구독 갱신을 대신하지 않습니다. 구독이 만료되었거나 모든 노드가 삭제된 상태에서 API만 반복 호출하면 장애를 해결할 수 없습니다. 먼저 구독 업데이트와 노드 목록을 확인하고, 그다음 API 연결, 개별 지연 측정, 그룹 전환, 실제 트래픽 순서로 검증하면 문제 범위를 빠르게 좁힐 수 있습니다.

점검 전에 클라이언트와 커널 버전 확인

이 글에서 다루는 proxy-server-nameserver, unified-delay, tcp-concurrent 세 필드는 mihomo 커널이 필요합니다. 원본 Clash 커널은 유지보수가 중단되었으므로 필드가 적용되지 않으면 먼저 커널 종류를 확인하고 로그와 대조해 항목별로 점검하세요.

다운로드 페이지로 이동 설치 가이드 보기

Clash 클라이언트 다운로드