Xray 라우팅 규칙을 직접 편집하려는 사용자를 위한 글입니다. 도메인과 IP 입력 방식의 차이부터 규칙 문법, 필드 조합, DNS 해석 전략까지 살펴본 뒤 확인 가능한 routing 설정 예제를 소개합니다. 글을 읽고 나면 연결이 왜 직결·프록시·차단 중 하나로 처리되는지, 규칙 수정 후 어떤 로그를 확인해야 하는지 파악할 수 있습니다. 예제에는 테스트용으로 예약된 도메인을 사용하며 실제 서비스의 인증 정보는 포함하지 않습니다.
라우팅 입력과 아웃바운드 태그
Xray 라우팅은 아웃바운드를 선택하는 기능이며, 애플리케이션 대신 요청을 생성하지 않습니다. 애플리케이션의 연결은 먼저 inbounds로 들어옵니다. 라우팅은 대상 주소, 포트, 네트워크 유형 등의 정보를 확인한 뒤 연결을 해당 outbounds로 전달합니다. v2rayN은 클라이언트 인터페이스입니다. 이 글의 코드는 Xray 코어용 JSON 설정이며 구독 링크로 가져올 수 없습니다.
각 규칙의 outboundTag는 실제로 존재하는 아웃바운드의 tag와 일치해야 합니다. 아래의 direct, proxy, block은 사용자가 정한 태그일 뿐, 이름을 입력한다고 기능이 자동으로 만들어지는 것은 아닙니다. 일반적으로 각각 직결 아웃바운드, 설정된 프록시 아웃바운드, 차단 아웃바운드에 연결합니다.
- 도메인이 있는 경우: SOCKS 또는 HTTP 프록시에 도메인을 전달하면 라우팅에서 보통 도메인 규칙을 바로 확인할 수 있습니다.
- IP만 있는 경우: 애플리케이션이 먼저 DNS 해석을 마친 뒤 IP를 프록시에 전달하면, 비교할 도메인이 없어 도메인 규칙을 적용할 수 없습니다.
- 스니핑을 사용하는 경우: 코어가 지원되는 핸드셰이크 정보에서 도메인을 추출할 수 있습니다. 추출한 도메인으로 대상을 덮어쓸지 라우팅에만 사용할지는 스니핑 설정에 따라 달라집니다.
- 일치하는 규칙이 없는 경우: 일반적인 Xray 설정에서는 첫 번째 아웃바운드를 사용합니다. 이 동작을 고정된 ‘기본 프록시’로 오해하지 마세요.
domain, full, regexp의 차이
도메인 매칭 항목은 규칙의 domain 배열에 넣습니다. 여기서 domain은 필드 이름이자 배열 요소에 붙이는 매칭 접두어이기도 합니다. 필드 이름은 ‘도메인을 확인한다’는 뜻이고, 접두어는 ‘어떤 방식으로 비교할지’를 지정합니다. 아래의 example.test는 예약된 테스트 도메인으로, 문법 설명용이며 실제 접속 가능한 서비스를 뜻하지 않습니다.
| 배열 요소 | 매칭 방식 | 범위와 주의 사항 |
|---|---|---|
full:api.example.test | 정확한 도메인 일치 | 해당 호스트와 일치하며 하위 도메인은 포함하지 않습니다. |
domain:example.test | 도메인과 하위 도메인 일치 | 루트 도메인과 api.example.test는 포함하지만 notexample.test는 포함하지 않습니다. |
example | 일반 문자열 매칭 | 부분 문자열로 비교하므로 도메인 접미사 매칭보다 범위가 넓은 경우가 많습니다. |
regexp:^api[0-9]+\\.example\\.test$ | 정규식 매칭 | JSON 문자열 표기이므로 백슬래시는 이스케이프해야 합니다. |
geosite:cn | 도메인 데이터 집합 매칭 | 로컬 도메인 데이터 파일에서 지정된 집합을 불러옵니다. |
특정 인터페이스 하나만 프록시로 보내려면 full:을 우선 사용하세요. 도메인 트리 전체를 포함하려면 domain:을 사용합니다. 일반 도메인 항목에 *.example.test를 범용 와일드카드처럼 입력하면 안 됩니다. 와일드카드 방식이 필요하다면 도메인 접미사 매칭이나 명시적인 정규식을 사용하세요.
{
"type": "field",
"domain": [
"full:api.example.test",
"regexp:^edge[0-9]+\\.example\\.test$"
],
"outboundTag": "proxy"
}
위 예제의 배열 요소 두 개는 ‘또는’ 관계이므로 하나만 일치해도 됩니다. 정규식에서 점은 문자 그대로의 점으로 표현해야 하며 JSON에서는 \\.로 씁니다. 앞뒤의 ^와 $는 매칭 범위를 제한해 호스트 이름 앞이나 뒤에 문자가 더 붙은 경우 의도치 않게 일치하지 않도록 합니다.
ip, geoip 및 geosite 데이터 집합
ip 필드는 대상 IP를 확인하며, 단일 주소나 CIDR 대역 또는 geoip: 집합을 지정할 수 있습니다. 도메인 집합은 domain에, IP 집합은 ip에 넣습니다. geosite와 geoip는 온라인 조회 서비스가 아니며 서로 다른 필드로 바꿔 쓸 수도 없습니다.
{
"type": "field",
"ip": [
"192.168.0.0/16",
"10.0.0.0/8",
"fc00::/7",
"geoip:private"
],
"outboundTag": "direct"
}
이 예제는 몇 가지 사설 네트워크 대역을 명시하고 geoip:private 집합을 추가로 사용합니다. 일부 범위는 겹치는데, 이는 문법을 비교하기 위한 것입니다. 실제 설정에서는 사용하는 데이터 파일에 맞춰 줄일 수 있습니다. 192.168.0.0/16은 네트워크 대역을 뜻하며 192.168.0.0 주소 하나만 매칭하는 것이 아닙니다.
geosite.dat에는 도메인 집합이,geoip.dat에는 IP 집합이 들어 있습니다. 현재 코어가 리소스 경로에서 해당 파일을 읽을 수 있어야 합니다.geosite:cn은 도메인 집합을,geoip:cn은 주소 집합을 나타내며 두 집합의 결과가 반드시 일치하는 것은 아닙니다.- 집합 이름과 속성 필터, 적용 범위는 실제 데이터 파일에 따라 달라집니다. 분류 이름만 보고 특정 사이트가 반드시 포함된다고 단정할 수 없습니다.
- 규칙을 수정한 뒤 코어가 시작되지 않으면 먼저 로그에서 파일 읽기 실패나 집합을 찾을 수 없다는 오류가 있는지 확인하세요. 노드를 바로 바꾸지는 마세요.
매칭 우선순위와 필드 조합
routing.rules는 배열 순서대로 검사하며, 조건을 만족하는 첫 번째 규칙이 아웃바운드를 결정합니다. 규칙이 더 구체적이라고 코어가 자동으로 앞쪽에 배치하지는 않습니다. 앞에 있는 포괄적인 규칙이 먼저 적용되면 뒤의 예외 규칙은 실행되지 않을 수 있습니다.
{
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"domain": ["full:api.example.test"],
"outboundTag": "proxy"
},
{
"type": "field",
"domain": ["domain:example.test"],
"outboundTag": "direct"
},
{
"type": "field",
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
이 설정에서는 api.example.test가 프록시를 사용하고 www.example.test는 직결됩니다. 나머지 TCP 및 UDP 연결은 마지막 규칙에 따라 프록시로 전달됩니다. 두 번째 규칙을 첫 번째 규칙보다 앞에 놓으면 API 도메인이 먼저 도메인 트리 규칙과 일치해 직결됩니다.
| 조합 위치 | 논리 관계 | 실제 의미 |
|---|---|---|
하나의 domain 배열에 있는 여러 항목 | 또는 | 항목 중 하나라도 일치하면 해당 필드의 조건을 만족합니다. |
하나의 ip 배열에 있는 여러 네트워크 대역 | 또는 | 주소가 어느 한 대역에 포함되면 해당 필드의 조건을 만족합니다. |
같은 규칙의 domain과 port | 그리고 | 도메인과 포트 조건을 모두 만족해야 합니다. |
같은 규칙의 domain과 ip | 그리고 | 도메인이나 IP 중 하나만 선택하는 조건이 아닙니다. IP 조건에는 대상 주소 정보와 DNS 해석 전략도 영향을 줍니다. |
정리: 예외 규칙을 집합 규칙보다 앞에 배치
단일 호스트에 대한 예외를 먼저 쓰고, 그다음 도메인 트리와 분류 집합을 배치한 뒤 마지막에 기본 규칙을 둡니다. ‘도메인 또는 IP가 일치하면 직결’이 목적이라면 두 필드를 한 규칙에 넣지 말고 규칙 두 개로 나누세요.
domainStrategy가 IP 매칭에 미치는 영향
routing.domainStrategy는 라우팅 단계에서 도메인에 IP 정보를 추가로 조회할지 결정합니다. 규칙 순서를 바꾸는 옵션도, DNS 서버 주소를 지정하는 필드도 아닙니다. 설정에 dns 섹션이 있어도 모든 도메인에 대해 라우팅이 자동으로 IP 매칭을 수행하지는 않습니다.
| 전략 | 라우팅 단계의 동작 | 유의 사항 |
|---|---|---|
AsIs | 라우팅 매칭을 위해 도메인을 먼저 해석하지 않습니다. | 대상이 원래 IP라면 IP 규칙과 매칭할 수 있습니다. |
IPIfNonMatch | 도메인이 규칙과 일치하지 않으면 그때 DNS 해석을 시도한 뒤 IP 조건이 포함된 규칙을 다시 확인합니다. | 첫 번째 검사에서 포괄적인 기본 규칙이 일치하면 ‘미일치’ 상태가 아니므로 DNS 해석이 실행되지 않습니다. |
IPOnDemand | IP 정보가 필요한 규칙을 만났을 때 도메인 해석을 시도한 뒤 판단을 계속합니다. | 앞쪽의 IP 규칙은 DNS 해석이 필요해질 수 있으며 뒤에 있는 도메인 규칙의 실행 여부에도 영향을 줄 수 있습니다. |
흔한 오해 중 하나는 IPIfNonMatch를 선택하고 앞쪽에 geoip:cn을 둔 뒤, 마지막에 TCP와 UDP 전체를 프록시로 보내는 규칙을 작성하면 모든 도메인이 먼저 IP 지역에 따라 분기될 것이라고 기대하는 것입니다. 도메인이 대상인 경우 첫 번째 검사에서 마지막 프록시 규칙이 바로 일치할 수 있습니다. 일단 일치하면 ‘미일치 후 DNS 해석’을 수행할 기회는 없습니다.
- 주로 도메인 집합을 기준으로 분기하려면 도메인 규칙을 명확하게 구성하고 IP만 있는 요청을 어떻게 처리할지도 확인하세요.
- 도메인 규칙이 일치하지 않을 때만 IP 기준으로 판단하려면
IPIfNonMatch를 사용하고, 앞에서 일치하는 일반 규칙이 있는지 확인하세요. - 앞쪽의 IP 조건도 도메인 요청 판단에 참여해야 한다면
IPOnDemand를 검토하고 DNS 응답과 규칙 순서가 예상대로인지 확인하세요.
라우팅 과정에서 얻은 IP를 실제 연결에 사용되는 최종 주소와 동일하다고 단정할 수 없습니다. 아웃바운드가 도메인을 계속 사용하는지, 어디서 DNS를 해석하는지는 사용하는 프로토콜과 아웃바운드 설정에도 영향을 받습니다. 문제를 확인할 때는 ‘라우팅이 왜 이 태그를 선택했는지’와 ‘연결이 실제로 어디로 향했는지’를 따로 검증하세요.
포트 기준 최소 테스트 구성 만들기
분류 규칙 수십 개를 한꺼번에 가져온 뒤 웹페이지가 열리는지 확인하지 마세요. 먼저 명확한 인바운드 포트, 포트 제한이 있는 도메인 규칙, 식별하기 쉬운 아웃바운드 태그로 매칭 경로를 검증합니다. 아래 세 숫자는 테스트 설정에 지정한 값이며 모든 클라이언트 버전의 기본값은 아닙니다.
{
"type": "field",
"domain": ["full:api.example.test"],
"port": "443",
"network": "tcp",
"outboundTag": "proxy"
}
이 규칙은 대상 도메인과 대상 포트 443, TCP 조건을 모두 만족하는 연결만 매칭합니다. 같은 호스트의 포트 80이나 UDP 443은 매칭하지 않습니다. port는 대상 포트를 확인하며 로컬 SOCKS 리스닝 포트인 10808을 확인하는 것이 아닙니다. 브라우저가 UDP 기반 전송을 사용한다면 이 TCP 규칙만으로 결과를 예측할 수 없습니다.
- 현재 작동하는 설정을 백업하고, 코어가 새 라우팅 설정을 읽었는지 확인하세요. 이전 프로세스가 계속 실행 중인 것은 아닌지도 확인해야 합니다.
- 로컬 인바운드가 실제로
127.0.0.1:10808에서 수신 대기하는지 확인하세요. 다른 포트를 사용한다면 실제 설정값을 기준으로 합니다. - 테스트 환경에서 예약된 도메인을 해석하거나 스니핑할 수 있도록 입력을 준비하세요. 테스트 서비스가 없다면 코어가 규칙을 불러오는지만 확인하고, 접속 실패를 라우팅 실패로 간주하지 마세요.
- DNS 동작을 비교해야 한다면 로그 수준을 잠시 높이고 요청을 한 번 다시 보내세요. 수신된 대상, DNS 조회, 아웃바운드 태그를 확인한 뒤 테스트가 끝나면 로그 수준을 원래대로 돌리세요.
- 한 번에 하나씩만 바꾸세요. 먼저 규칙 순서를 조정하고, 다음으로
domainStrategy를 바꾼 뒤 마지막으로 데이터 집합을 추가합니다. 각 단계의 결과를 남겨 여러 변경 사항이 서로 원인을 가리지 않도록 하세요.
v2rayN 라우팅 문제 해결 FAQ
v2rayN에서 라우팅을 조정한 뒤에는 현재 선택된 라우팅 설정과 코어 유형도 확인해야 합니다. 이 글은 Xray의 필드 의미를 기준으로 설명합니다. 인터페이스의 라우팅 편집 항목은 클라이언트가 변환한 뒤 생성 설정에 기록될 수 있으므로, JSON 전체를 도메인 항목만 받는 입력란에 그대로 붙여넣지 마세요.
full 규칙을 추가했는데 왜 기존 아웃바운드로 연결되나요?
해당 규칙을 포괄적인 도메인 집합 규칙과 기본 규칙보다 앞에 놓고 설정을 다시 불러오세요. 로그에서 대상이 여전히 도메인으로 표시되는지 확인합니다. 대상이 IP만 있다면 애플리케이션이 자체적으로 DNS를 해석했는지, 인바운드 스니핑이 실제로 사용할 수 있는 도메인을 제공했는지 살펴보세요.
geosite 규칙을 저장한 뒤 코어가 시작되지 않는 이유는 무엇인가요?
코어 시작 로그에서 데이터 파일을 읽을 수 있는지, 집합 이름이 존재하는지, 규칙이 올바른 필드에 들어갔는지 각각 확인하세요. 먼저 full: 규칙 하나로 라우팅을 검증한 뒤 집합 규칙을 다시 추가하면 문법 문제와 데이터 파일 문제를 구분할 수 있습니다.
IPIfNonMatch를 설정했는데 예상한 DNS 조회가 보이지 않는 이유는 무엇인가요?
첫 번째 검사에서 도메인 규칙이나 network: tcp,udp 기본 규칙이 이미 일치했는지 확인하세요. 이미 일치했다면 ‘미일치’ 조건이 충족되지 않습니다. IP 조건을 먼저 적용해야 한다면 IPOnDemand를 검토하세요. IP 규칙을 옮기는 것만으로 결과를 추측하지 마세요.
한 규칙에 domain과 ip를 함께 썼더니 매칭이 줄어든 이유는 무엇인가요?
두 필드의 조건을 모두 만족해야 합니다. 도메인 집합이나 주소 집합 중 하나만 일치해도 직결하려는 것이었다면 규칙을 둘로 나누고 두 규칙에 같은 outboundTag를 지정하세요. 그런 다음 도메인 대상과 IP 대상을 각각 테스트합니다.
생성된 설정을 수정했는데 클라이언트를 다시 시작하니 원래대로 돌아왔습니다. 어떻게 해야 하나요?
노드를 전환하거나 코어를 다시 시작할 때 자동 생성된 설정이 다시 만들어질 수 있습니다. 클라이언트가 제공하는 라우팅 설정 메뉴에서 규칙을 저장하거나 지원되는 사용자 지정 설정 방식을 사용하세요. 다시 시작한 뒤에는 코어가 실제로 불러온 내용을 다시 확인해야 합니다. 편집기에서 저장에 성공했다는 사실만으로 적용 여부를 판단하지 마세요.
최종 확인 순서는 고정해 두세요. 입력 대상이 무엇인지, 규칙 조건을 만족하는지, 가장 먼저 일치한 규칙은 무엇인지, 태그가 존재하는지, 아웃바운드가 연결되는지 차례로 확인합니다. 웹페이지가 열린 사실만으로 라우팅이 올바르다고 단정할 수는 없습니다. 여러 라우팅 모드를 동시에 바꾸기보다 최소 테스트 규칙과 해당 로그를 남겨야 문제를 더 쉽게 찾을 수 있습니다.