Xray를 여러 노드로 운영할 때 “현재 연결되어 있다”는 상태만으로는 출구 품질을 판단하기 어렵습니다. 원격 포트는 열려 있지만 TLS 또는 REALITY 핸드셰이크가 실패할 수 있고, 연결은 성공해도 왕복 시간이 급증하거나 패킷 손실 때문에 실제 웹 요청이 자주 끊길 수 있습니다. 이때 Xray API를 관리용 로컬 포트에만 노출하고, 별도의 점검 스크립트가 노드별 응답 시간과 오류 횟수를 기록한 뒤 문제가 있는 출구를 격리하는 구조를 사용할 수 있습니다.
다만 Xray API가 모든 노드의 인터넷 지연 시간을 자동으로 측정해 주는 것은 아닙니다. StatsService는 업로드·다운로드 통계와 연결 활동을 조회하는 기능이고, 실제 노드 응답 시간은 각 아웃바운드를 통과하는 테스트 요청으로 측정해야 합니다. 따라서 안정적인 구성은 “API로 상태를 읽고 관리 명령을 전달하는 Xray”와 “측정·판정·설정 교체를 담당하는 외부 스크립트”를 분리하는 방식입니다. 이 글에서는 무인 Linux 서버에서 이 흐름을 systemd와 JSON 설정으로 구성하는 방법을 설명합니다.
Xray API를 로컬 주소에 바인딩하고 StatsService로 코어 상태를 확인한 뒤, 노드별 프록시 테스트를 60초 간격으로 실행해 지연 시간과 연속 실패 횟수를 판정합니다. 임계값을 넘은 노드는 백업 목록으로 격리하고, 새 설정을 문법 검증한 후 원자적으로 적용해 정상 노드로 전환하는 운영 절차를 다룹니다.
API의 역할과 자동 전환 구조
Xray API는 일반 사용자 트래픽을 전달하는 프록시 포트와 분리된 관리 채널입니다. API 인바운드는 127.0.0.1:10085처럼 로컬 루프백 주소에만 열어 외부에서 직접 접근할 수 없게 해야 합니다. 설정에는 API 태그를 등록하고, 해당 인바운드에서 들어온 요청이 일반 프록시 아웃바운드로 빠지지 않도록 api 아웃바운드로 보내는 라우팅 규칙을 함께 둡니다.
자동 전환은 다음 네 계층으로 나누면 장애 원인을 추적하기 쉽습니다. 첫째, Xray 프로세스가 살아 있고 API가 응답하는지 확인합니다. 둘째, 각 노드에 연결되는 전용 테스트 경로를 이용해 실제 왕복 시간을 측정합니다. 셋째, 최근 결과를 기준으로 정상·주의·격리 상태를 판정합니다. 넷째, 활성 노드를 바꾼 설정을 검증하고 Xray를 재시작한 다음 실제 프록시 요청으로 전환 결과를 확인합니다.
중요한 점은 단 한 번의 시간 초과만으로 노드를 제거하지 않는 것입니다. 일시적인 회선 혼잡이나 테스트 대상의 응답 지연이 장애로 오인될 수 있기 때문입니다. 예를 들어 60초마다 세 차례 테스트하고, 3회 연속 실패하거나 1800ms를 넘는 결과가 3회 연속이면 격리합니다. 반대로 격리된 노드는 5분마다 다시 확인해 두 번 연속 정상일 때 후보 목록으로 복귀시키는 식으로 복구 조건도 별도로 정의해야 합니다.
Xray JSON에 API와 통계를 추가하기
다음은 Xray 설정의 핵심 부분을 단순화한 예시입니다. 기존에 사용하는 VLESS, VMess 또는 다른 아웃바운드 정의는 생략했으며, 실제 서버의 주소·포트·UUID·전송 보안 매개변수로 교체해야 합니다. API 인바운드는 일반 프록시 인바운드와 포트를 분리하고, 통계가 필요한 사용자 또는 레벨에 정책 항목을 추가합니다.
관리 API
- 주소
- 127.0.0.1
- 포트
- 10085/tcp
- 태그
- api
외부 인터페이스에 바인딩하지 않습니다.
점검 인바운드
- 주소
- 127.0.0.1
- 예시 포트
- 10808/tcp
- 용도
- 노드별 테스트
스크립트가 SOCKS 또는 HTTP 요청을 보냅니다.
{
"api": {
"services": ["HandlerService", "LoggerService", "StatsService"],
"tag": "api"
},
"stats": {},
"policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"inbounds": [
{
"listen": "127.0.0.1",
"port": 10085,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
},
"tag": "api"
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
}
]
}
}
실제 설정에는 API 요청을 처리할 api 아웃바운드도 필요합니다. 또한 StatsService에서 사용자별 통계를 조회하려면 해당 사용자에 통계 식별자가 포함되어야 합니다. 구독에서 가져온 노드에는 이미 사용자 식별자가 존재할 수 있지만, 태그가 없는 여러 아웃바운드를 자동 생성할 때는 각 노드의 태그를 고유하게 지정해야 합니다. 예를 들어 node-seoul-01, node-tokyo-01처럼 운영용 이름을 정하고 설정 파일 전체에서 중복되지 않게 유지하세요.
API 서비스를 추가한 후에는 곧바로 재시작하지 말고 먼저 설정 검사를 실행합니다. 배포된 Xray 버전에 따라 명령 형식이 다를 수 있으므로 실제 실행 파일의 도움말을 확인해야 합니다. 일반적으로 다음과 같이 검사 결과가 성공한 뒤 systemd를 다시 불러옵니다.
sudo xray run -test -config /etc/xray/config.json
sudo systemctl restart xray
sudo systemctl status xray --no-pager
sudo journalctl -u xray -n 80 --no-pager
노드 측정과 장애 판정 기준
노드별 지연 시간을 측정할 때는 서버 주소에 단순히 TCP 연결만 시도하는 것과 실제 프록시 요청을 보내는 것을 구분해야 합니다. TCP 연결 성공은 원격 포트까지 도달했다는 뜻일 뿐이며, UUID, flow, TLS 서버 이름, Reality 공개 키와 shortId가 올바르다는 의미는 아닙니다. 자동 전환의 기준으로 사용할 값은 각 노드 아웃바운드를 실제로 통과한 HTTPS 요청의 총 소요 시간이나 정해진 테스트 엔드포인트의 응답 시간이어야 합니다.
노드가 여러 개라면 각 노드마다 임시 테스트 인바운드를 만드는 대신, 설정에 미리 노드별 아웃바운드와 태그를 등록하고 테스트 스크립트가 지정된 경로를 사용하도록 설계하는 편이 관리하기 좋습니다. 테스트 대상은 응답 본문이 작은 HTTPS 주소로 고정하고, 리다이렉트가 너무 많거나 지역별 응답 차이가 큰 주소는 피하세요. 측정 결과에는 시각, 노드 태그, HTTP 상태, DNS 소요 시간, 연결 소요 시간, 전체 지연 시간과 오류 원인을 함께 기록하는 것이 좋습니다.
| 상태 | 판정 예시 | 동작 |
|---|---|---|
| 정상 | 200 응답, 1800ms 미만, 연속 실패 0~1회 | 활성 후보로 유지 |
| 주의 | 1800ms 이상 또는 일시적 시간 초과 1~2회 | 계속 측정하고 즉시 제거하지 않음 |
| 격리 | 3회 연속 실패 또는 3000ms 초과 반복 | 활성 목록에서 제외 |
| 복구 대기 | 격리 후 2회 연속 정상 | 백업 후보로 복귀 |
결론: 낮은 지연 시간보다 판정 일관성이 우선입니다
한 번 400ms가 나온 노드보다 900ms가 꾸준히 유지되는 노드가 실제 운영에서는 더 안정적일 수 있습니다. 자동 전환은 최저 기록 경쟁이 아니라 연속 실패, 회복 확인, 전환 후 검증을 포함한 상태 관리로 설계해야 불필요한 플랩을 줄일 수 있습니다.
스크립트와 systemd로 자동화하기
운영 스크립트는 Xray 설정을 직접 문자열 치환하는 방식보다 JSON 파서로 읽고 쓰는 방식이 안전합니다. 스크립트의 기본 순서는 설정 파일 백업, Xray API 또는 로컬 테스트 경로 확인, 노드별 측정, 상태 파일 갱신, 새 활성 노드 선택, 임시 JSON 저장, 문법 검사, 원자적 파일 교체, systemd 재시작, 전환 후 재검증입니다. 현재 활성 노드가 여전히 정상이라면 더 낮은 지연 시간의 노드가 있다는 이유만으로 매분 전환하지 말고, 최소 유지 시간과 히스테리시스를 두세요.
API를 로컬에 고정
/etc/xray/config.json의 API 인바운드를127.0.0.1:10085로 설정하고HandlerService,LoggerService,StatsService를 등록합니다.노드 태그를 분리
각 아웃바운드에 중복되지 않는 태그를 부여하고, 테스트 대상과 운영용 기본 출구를 JSON에서 명확히 구분합니다.
결과를 누적 저장
스크립트가 60초마다 노드별 응답 시간과 실패 횟수를
/var/lib/xray/node-health.json에 기록하도록 합니다.새 설정을 임시 생성
선택된 노드의 라우팅 또는 기본 아웃바운드를 임시 파일에 기록하고
xray run -test로 문법과 필수 필드를 확인합니다.검증 후 전환
검사가 성공했을 때만
mv로 설정을 교체하고systemctl restart xray를 실행한 뒤 실제 프록시 요청으로 새 출구를 확인합니다.
systemd 타이머는 무인 서버에서 cron보다 실행 결과와 권한을 관리하기 쉽습니다. 예를 들어 점검 서비스를 별도 사용자로 실행하고, 설정 파일 교체와 Xray 재시작에 필요한 권한만 sudoers에서 허용할 수 있습니다. 점검 스크립트에 전체 root 권한을 주면 노드 데이터가 변조되었을 때 영향 범위가 커지므로 실행 사용자, 설정 디렉터리, 상태 디렉터리의 권한을 각각 제한하세요.
# /etc/systemd/system/xray-node-check.service
[Unit]
Description=Check Xray node health and switch active route
After=xray.service
Requires=xray.service
[Service]
Type=oneshot
User=xray-check
ExecStart=/usr/local/sbin/xray-node-check
# /etc/systemd/system/xray-node-check.timer
[Unit]
Description=Run Xray node health check every 60 seconds
[Timer]
OnBootSec=2min
OnUnitActiveSec=60s
Persistent=true
[Install]
WantedBy=timers.target
자동 재시작이 반복될 때는 로그를 반드시 확인해야 합니다. 설정 교체 직후 API가 사라지면 새 설정이 로드되지 않았거나 Xray가 시작 단계에서 종료된 것입니다. 이 경우 스크립트는 이전 설정으로 즉시 되돌릴 수 있어야 합니다. 또한 전환 이벤트를 “현재 노드 A에서 B로 변경”, “노드 C는 3회 실패로 격리”처럼 구조화해 기록하면 단순한 연결 끊김과 자동화 로직의 오류를 구분할 수 있습니다.
권장 운영 방식: 측정과 적용을 분리하세요
점검 프로세스
- 노드별 테스트 요청 실행
- 60초 주기 상태 누적
- 실패 횟수와 복구 상태 기록
Xray 프로세스
- API와 프록시 트래픽 처리
- 검증된 설정만 로드
- 재시작 후 API 응답 확인
점검 스크립트가 Xray 내부 처리와 분리되어야 스크립트 오류가 전체 프록시 프로세스를 직접 손상시키지 않습니다.
전환 후 검증과 자주 묻는 질문
노드 전환이 끝났다는 메시지만으로 성공을 판단하지 마세요. 먼저 systemctl is-active xray로 프로세스 상태를 확인하고, API 포트가 로컬에서 응답하는지 확인합니다. 다음으로 점검용 프록시 포트를 통해 작은 HTTPS 요청을 실행하고, Xray 액세스 로그 또는 통계에서 새 아웃바운드 태그에 트래픽이 증가하는지 살펴봅니다. 새 노드가 정상으로 보이더라도 DNS 규칙, 직접 연결 예외, UDP 트래픽은 별도로 검증해야 합니다. HTTP 테스트 하나가 모든 애플리케이션의 정상 작동을 보장하지는 않습니다.
Xray API가 지연 시간을 직접 반환하나요?
아닙니다. StatsService는 트래픽 통계와 연결 활동을 조회합니다. 노드별 지연 시간은 해당 아웃바운드를 통과하는 테스트 요청으로 측정하고, 결과를 스크립트가 저장해야 합니다.
한 번 시간 초과된 노드를 바로 제거해도 되나요?
권장하지 않습니다. 60초 간격으로 최소 3회 확인하고 연속 실패 횟수, 전체 지연 시간, 테스트 대상 응답 상태를 함께 판정해야 일시적인 혼잡으로 인한 오전환을 줄일 수 있습니다.
API로 현재 라우팅 노드를 즉시 바꿀 수 있나요?
API의 HandlerService는 인바운드·아웃바운드 관리에 사용할 수 있지만 일반적인 운영에서는 라우팅 설정을 외부 스크립트가 생성하고 검증한 뒤 Xray를 다시 불러오는 방식이 더 명확합니다. 사용 중인 Xray 버전의 API 지원 범위를 먼저 확인하세요.
전환 후에도 연결이 이전 노드로 보이는 이유는 무엇인가요?
기존 연결은 전환 시점에 즉시 사라지지 않을 수 있습니다. 새 연결을 만들고, 라우팅 규칙의 순서와 아웃바운드 태그를 확인한 뒤 액세스 로그에서 새 태그가 실제로 사용되는지 점검하세요.