config.json 구조 단계별 해설: inbounds, outbounds, routing의 역할
최소 실행 구성 파일을 바탕으로 inbounds의 로컬 수신, outbounds의 원격 연결, routing의 출구 선택과 tag를 통한 연결 구조를 설명합니다.
v2rayN 구독 기능으로 생성했든 관리자가 직접 정리했든, 핵심 구조는 세 가지 질문으로 요약할 수 있습니다. 트래픽은 어디에서 들어오고, 어디로 나가며, 어떤 조건에 따라 출구가 결정되는가입니다. 이 세 가지를 이해하면 “포트에는 연결되지만 웹페이지가 열리지 않음”, “직접 연결할 도메인도 프록시를 거침”, “차단 규칙이 적용되지 않음” 같은 문제를 더 빠르게 점검할 수 있습니다.
이 글은 구독을 가져올 수 있고 내부 설정까지 이해하고 싶은 사용자에게 적합합니다. 읽고 나면 inbounds, outbounds, routing, tag의 역할을 구분하고, SOCKS 인바운드, VMess 프록시 출구, 직접 연결 출구, 차단 출구가 포함된 설정을 해석할 수 있습니다. 또한 로그 순서에 따라 규칙 미적용, 포트 충돌, 출구 참조 오류를 찾아낼 수 있습니다.
먼저 전체 트래픽 경로 이해하기: 인바운드, 매칭, 아웃바운드
애플리케이션이 바로 routing으로 들어가는 것은 아닙니다. 브라우저나 다른 프로그램이 먼저 로컬 리스닝 포트로 요청을 보내면 inbounds가 연결을 수신하고 코어가 처리할 대상 정보를 만듭니다. routing은 대상 도메인, 대상 IP, 포트, 네트워크 유형, 인바운드 태그를 읽습니다. 매칭에 성공하면 규칙의 outboundTag가 특정 outbounds 항목을 가리킵니다. 어떤 규칙에도 일치하지 않으면 일반적으로 outbounds 배열의 첫 번째 항목이 기본 출구로 사용되므로 배열 순서도 설정 로직의 일부입니다.
config.json은 서로 무관한 매개변수 모음이 아니라 방향성을 가진 연결 그래프로 볼 수 있습니다. inbounds의 tag는 트래픽 출처를 표시하고, outbounds의 tag는 출구 이름을 지정하며, routing.rules가 이 이름을 참조해 연결을 완성합니다. tag 자체가 데이터를 전송하거나 자동으로 프록시 체인을 만드는 것은 아닙니다. 코어 내부에서 안정적으로 사용되는 대소문자 구분 참조 키일 뿐입니다.
inbounds: 로컬 트래픽 수신 방식 정의
inbounds는 인바운드 배열이며, 각 항목은 하나의 리스닝 진입점을 나타냅니다. 데스크톱 환경에서는 SOCKS, HTTP 또는 클라이언트가 관리하는 투명 프록시 진입점이 흔히 사용됩니다. 가장 기본적인 SOCKS 인바운드는 listen, port, protocol, settings를 명확히 지정해야 합니다. 리스닝 주소를 127.0.0.1로 설정하면 로컬 프로그램만 접근할 수 있습니다. 0.0.0.0으로 바꾸면 모든 네트워크 인터페이스에서 수신하므로 같은 네트워크의 다른 기기도 해당 포트에 접근할 수 있습니다. 두 설정을 같은 의미로 보면 안 됩니다.
{
"inbounds": [
{
"tag": "socks-in",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
},
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
]
}
}
]
}
인바운드 설정을 필드별로 읽기
tag: 이 진입점의 이름을socks-in으로 지정합니다. 이후 규칙은inboundTag를 사용해 이 진입점에서 온 요청만 처리할 수 있습니다.listen: 바인딩할 주소를 지정합니다.127.0.0.1은 로컬 루프백 주소로, 현재 컴퓨터에서만 사용하는 클라이언트 설정에 적합합니다.port: 리스닝 포트를 지정합니다. 예시에서는 10808을 사용하므로 브라우저나 애플리케이션의 SOCKS5 프록시 포트에도 10808을 입력해야 합니다.protocol: 애플리케이션과 로컬 코어가 통신하는 방식을 선언합니다. 여기서는 SOCKS이며, 원격 노드도 SOCKS를 사용한다는 뜻은 아닙니다.settings.udp: SOCKS 인바운드가 UDP 요청을 수신하도록 허용합니다. 이는 인바운드 기능만 활성화하며, 실제 사용 가능 여부는 원격 프로토콜, 전송 방식, 대상 애플리케이션에 따라 달라집니다.sniffing: HTTP 요청이나 TLS 핸드셰이크에서 대상 도메인을 복원하여 도메인 기반 분기 규칙이 적용될 수 있게 합니다.
포트 충돌은 코어가 리스닝 소켓을 만들 때 발생합니다. 10808을 다른 프로세스가 사용 중이면 inbounds는 트래픽 수신을 시작하지 못하고, 이후의 routing과 outbounds도 실행되지 않습니다. v2rayN에서 「설정」→「매개변수 설정」으로 들어가 로컬 SOCKS 포트를 변경할 수 있습니다. 10818로 바꿨다면 브라우저 프록시, 시스템 프록시 또는 다른 애플리케이션의 포트도 10818로 함께 변경해야 합니다.
outbounds: 프록시, 직접 연결, 차단 출구 정의
outbounds는 아웃바운드 배열입니다. 각 출구에는 고유한 tag가 있어야 하며, protocol이 코어의 트래픽 처리 방식을 결정합니다. 원격 프록시 출구에는 서버 주소, 포트, 사용자 식별자, 전송 매개변수가 포함됩니다. 직접 연결 출구는 freedom을 사용하고, 연결을 능동적으로 종료하는 출구는 blackhole을 사용합니다. 세 가지 출구를 동시에 정의한 뒤 routing으로 실제 사용할 출구를 선택할 수 있습니다.
proxy: VMess 프록시 출구
- 프로토콜
- VMess
- 서버 포트
- 443
- 전송 방식
- WebSocket
- 전송 보안
- TLS
노드 매개변수는 일반적으로 구독에서 가져오며, address, id, 경로, 전송 보안 설정은 서버 측과 일치해야 합니다.
direct: 직접 연결
- 프로토콜
- freedom
- 원격 노드
- 필요 없음
- 주요 용도
- 로컬 네트워크 및 지정 도메인
- 참조 태그
- direct
직접 연결은 proxy 아웃바운드를 거치지 않고 로컬 네트워크에서 대상에 직접 접속하는 방식입니다.
block: 연결 종료
- 프로토콜
- blackhole
- 원격 연결
- 생성하지 않음
- 주요 용도
- 광고 도메인 규칙
- 참조 태그
- block
매칭되면 코어가 요청을 종료하므로, 명확히 차단할 대상 집합에 적합합니다.
기본 출구
- 결정 방식
- 배열의 첫 번째 항목
- 예시 태그
- proxy
- 적용 조건
- 규칙에 매칭되지 않음
- 점검 핵심
- 배열 순서
분류되지 않은 트래픽을 프록시로 보내려면 proxy를 outbounds의 첫 번째 항목에 배치해야 합니다.
{
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "node.example.net",
"port": 443,
"users": [
{
"id": "11111111-1111-4111-8111-111111111111",
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"wsSettings": {
"path": "/connection"
}
}
},
{
"tag": "direct",
"protocol": "freedom",
"settings": {}
},
{
"tag": "block",
"protocol": "blackhole",
"settings": {}
}
]
}
위에 나온 도메인, 사용자 식별자, 경로는 구조를 보여 주기 위한 예시이며 구독에 포함된 실제 노드 매개변수를 대신할 수 없습니다. VMess의 settings는 사용자와 서버를 설명하고, streamSettings는 전송 계층을 설명합니다. 주소와 포트가 올바르더라도 WebSocket 경로, TLS 설정 또는 사용자 식별자가 일치하지 않으면 연결에 실패합니다.
결론: 기본 출구를 먼저 확인한 뒤 복잡한 규칙을 점검하세요
routing을 일시적으로 비활성화해도 접속할 수 없다면 문제는 proxy 아웃바운드 매개변수나 네트워크 연결에 있을 가능성이 큽니다. routing을 끈 뒤 정상으로 돌아오면 규칙 순서, 매칭 조건, outboundTag 참조를 다시 확인해야 합니다.
routing: 조건에 따라 요청을 지정된 출구로 전달
routing은 리스닝을 만들거나 원격 노드 인증 정보를 저장하지 않습니다. 요청의 특성을 읽고 아웃바운드 태그를 반환하는 것이 역할입니다. rules 배열은 위에서 아래 순서로 매칭되며, 일반적으로 유효한 첫 규칙이 적용되면 이후 규칙은 계속 확인하지 않습니다. 따라서 범위가 좁고 우선순위가 높은 규칙은 앞에, 범위가 넓은 기본 규칙은 뒤에 배치해야 합니다.
{
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"domain": [
"geosite:category-ads-all"
],
"outboundTag": "block"
},
{
"type": "field",
"ip": [
"geoip:private"
],
"outboundTag": "direct"
},
{
"type": "field",
"inboundTag": [
"socks-in"
],
"network": "tcp,udp",
"outboundTag": "proxy"
}
]
}
}
세 규칙은 각각 어떤 문제를 해결할까
- 첫 번째 규칙은 GeoSite 분류를 읽고 광고 도메인과 매칭되면
block으로 전달합니다. 이 규칙이 작동하려면 로컬 GeoSite 데이터를 코어가 정상적으로 읽을 수 있어야 합니다. - 두 번째 규칙은 사설 IP 대역과 매칭되면
direct으로 전달하여 로컬 네트워크 주소가 원격 프록시 출구로 우회되지 않게 합니다. - 세 번째 규칙은 출처를
socks-in으로 제한하고, 나머지 TCP 및 UDP 요청을proxy로 전달합니다. 범위가 넓으므로 정확한 규칙 뒤에 배치합니다.
| 필드 | 확인 대상 | 일반적인 값 | 자주 발생하는 문제 |
|---|---|---|---|
domain |
대상 도메인 | domain:、full:、geosite: |
분류 데이터가 오래되어 도메인이 예상 집합에 포함되지 않음 |
ip |
대상 IP | CIDR、geoip:private |
도메인이 IP로 해석되지 않아 규칙 매칭에 참여하지 않음 |
port |
대상 포트 | 53, 80,443, 포트 범위 |
로컬 리스닝 포트를 대상 포트로 잘못 이해함 |
network |
전송 유형 | tcp、udp、tcp,udp |
tcp만 지정하고 UDP 요청을 누락함 |
inboundTag |
트래픽 출처 | socks-in |
참조 이름이 inbounds의 tag와 일치하지 않음 |
outboundTag |
대상 출구 | proxy、direct、block |
outbounds에 존재하지 않는 태그를 참조함 |
domainStrategy는 도메인 규칙과 IP 규칙 사이의 해석 전략을 제어합니다. AsIs는 원래 도메인을 우선 매칭하며 모든 IP 규칙을 위해 도메인을 자동으로 해석하지 않습니다. IPIfNonMatch는 먼저 도메인 규칙을 시도하고, 매칭되지 않으면 IP를 해석하여 IP 규칙을 계속 확인합니다. IPOnDemand는 매칭 과정에서 더 적극적으로 해석을 시작합니다. 일반적인 분기 설정은 IPIfNonMatch부터 시작하는 경우가 많으며, 도메인 분류 기능을 유지하면서 사설 IP 같은 규칙도 적용할 수 있습니다.
결론: 규칙 수보다 순서가 중요합니다
광고 차단과 로컬 네트워크 직접 연결을 먼저 배치하고, 모든 TCP 및 UDP를 포괄하는 프록시 규칙은 뒤에 두세요. 범위가 넓은 규칙이 앞에 나오면 이후의 더 정확한 direct 또는 block 규칙이 실행될 기회를 잃을 수 있습니다.
tag로 세 가지 설정을 하나의 연결 경로로 묶는 방법
tag의 핵심 조건은 “정의되어 있고, 철자가 일치하며, 용도가 명확해야 한다”는 것입니다. 인바운드 태그가 socks-in이고 세 아웃바운드 태그가 각각 proxy, direct, block이라면 routing은 이미 정의된 이 네 이름만 참조할 수 있습니다. outboundTag를 Proxy나 proxy-main으로 작성해도 proxy와 자동으로 연결되지 않습니다.
inbounds[0].tag = "socks-in"
routing.rules[2].inboundTag = ["socks-in"]
outbounds[0].tag = "proxy"
routing.rules[2].outboundTag = "proxy"
outbounds[1].tag = "direct"
routing.rules[1].outboundTag = "direct"
outbounds[2].tag = "block"
routing.rules[0].outboundTag = "block"
요청이 실제로 거치는 순서대로 점검하기
- 애플리케이션의 프록시 유형이 SOCKS5인지, 주소가 127.0.0.1인지, 포트가 inbounds.port와 같은지 확인합니다. 예시 포트는 10808입니다.
- 코어 로그에 “주소가 이미 사용 중” 또는 설정 해석 실패 메시지가 없는지 확인합니다. 리스닝에 실패했다면 원격 프로토콜을 계속 점검하지 마세요.
- 요청이 예상한 인바운드에서 들어오는지 확인합니다. 규칙이
inboundTag를 제한한다면 다른 HTTP 인바운드로 들어온 요청은 해당 규칙과 매칭되지 않습니다. - routing.rules의 배열 순서대로 첫 번째 매칭 항목을 찾고 해당
outboundTag를 기록합니다. - outbounds에서 이름이 완전히 같은 tag를 찾고, 해당 항목의 protocol, 서버 주소, 포트, streamSettings를 확인합니다.
- 어떤 규칙에도 매칭되지 않는다면 outbounds의 첫 번째 항목이 실제로 사용할 기본 출구인지 확인합니다.
출처별로 정책을 나누려면 인바운드를 여러 개 설정할 수 있습니다. 예를 들어 10808의 socks-in은 기본적으로 proxy를 사용하고, 10818의 다른 SOCKS 인바운드는 기본적으로 direct를 사용하게 할 수 있습니다. 이때 두 진입점은 서로 다른 port와 tag를 사용하고, 각각 inboundTag를 포함한 규칙을 작성해야 합니다. 인바운드만 복사하고 같은 포트를 유지하면 시작 단계에서 리스닝 충돌이 발생합니다.
설정 수정 시 안전한 절차와 자주 묻는 문제
v2rayN은 현재 노드, 라우팅 설정, 코어 옵션을 바탕으로 실행 설정을 생성합니다. 생성된 파일을 직접 편집하면 현재 프로세스에서만 적용될 수 있으며, 노드 전환, 구독 업데이트, 클라이언트 재시작 후 파일이 다시 생성될 수 있습니다. 장기간 유지할 라우팅 로직은 클라이언트에서 제공하는 라우팅 설정으로 관리하고, 로컬 포트를 조정할 때는 「설정」→「매개변수 설정」을 사용한 뒤 해당 포트를 호출하는 모든 애플리케이션도 함께 변경하세요.
- 수정하기 전에 현재 정상 작동하는 설정 사본을 저장하고, 매번 하나의 필드 그룹만 변경하세요.
- JSON을 편집한 뒤 쉼표, 따옴표, 배열과 객체의 닫힘 관계를 먼저 확인한 다음 코어를 시작하세요.
- outbounds 순서를 바꿀 때는 첫 번째 항목의 tag를 기록하세요. 규칙에 매칭되지 않을 때 사용할 기본 출구와 관련되기 때문입니다.
- routing.rules를 조정할 때는 정확한 규칙부터 범위가 넓은 규칙 순서로 배치하고, 각 규칙의 예상 출구를 기록하세요.
- 구독을 업데이트한 뒤 프로토콜, 전송 방식, TLS, 서버 포트, 사용자 식별자가 모두 정상적으로 가져와졌는지 다시 확인하세요.
- 라우팅 분류 결과가 이상하면 현재 코어가 GeoIP 및 GeoSite 데이터를 읽을 수 있는지 확인하세요.
포트 10808에는 연결되는데 왜 웹페이지가 열리지 않나요?
로컬 포트에 연결된다는 것은 inbounds가 리스닝 중이라는 뜻일 뿐입니다. 로그 수준을 잠시 info로 설정하고 요청이 proxy와 매칭되는지, VMess 아웃바운드의 서버 주소, 443 포트, 사용자 식별자, WebSocket 경로, TLS 설정이 구독 내용과 일치하는지 확인하세요.
direct 규칙을 작성했는데 왜 대상 도메인이 여전히 프록시를 사용하나요?
먼저 해당 규칙이 범위가 넓은 proxy 규칙보다 앞에 있는지 확인한 다음 domainStrategy와 매칭 유형을 확인하세요. IP 기준으로 분류하는 규칙이라면 전략을 IPIfNonMatch로 설정하고, 도메인 해석 결과가 실제로 대상 IP 범위에 포함되는지 확인하세요.
outboundTag는 아무 이름이나 사용해도 되나요?
이름은 직접 정할 수 있지만 outbounds 항목 중 하나의 tag와 완전히 일치해야 하며 중복을 피해야 합니다. proxy, direct, block처럼 역할이 분명한 짧은 이름을 권장합니다. 이름을 변경할 때는 모든 routing 참조도 함께 검색해 수정하세요.
구독을 업데이트하면 수동으로 설정한 라우팅이 왜 사라지나요?
실행 설정이 v2rayN에 의해 다시 생성되었을 수 있습니다. 장기간 사용할 규칙은 임시로 생성된 config.json만 수정하지 말고 클라이언트의 라우팅 설정에 저장하세요. 업데이트 후 규칙 순서와 아웃바운드 태그가 그대로 유지되는지도 확인해야 합니다.
VMess와 VLESS의 설정 구조는 완전히 같은가요?
최상위 구조는 여전히 inbounds, outbounds, routing을 사용하지만 아웃바운드 protocol, settings의 사용자 필드, streamSettings 조합은 달라질 수 있습니다. protocol 문자열만 바꾸지 말고 구독에서 가져온 전체 노드 매개변수를 기준으로 설정하세요.
config.json을 이해하는 핵심은 모든 필드를 외우는 것이 아니라 일정한 점검 경로를 유지하는 데 있습니다. 인바운드가 리스닝 중인지, 요청에 어떤 대상 정보가 담겼는지, 어느 규칙이 먼저 매칭되는지, outboundTag가 어떤 출구를 가리키는지, 해당 출구가 연결을 완료할 수 있는지를 순서대로 확인하세요. 포트, 프로토콜, 라우팅을 동시에 수정하는 것보다 이 경로를 따라 단계별로 검증하는 편이 원인을 찾기 쉽습니다.