V2Ray config.json 구조 해설: inbounds·outbounds·routing 항목별 분석

완성된 config.json을 바탕으로 인바운드 리스닝, 아웃바운드 tag, 라우팅 규칙의 매칭 순서와 tag 연결 방식을 설명합니다.

이 글의 핵심 내용

노드를 가져오는 방법은 알지만 설정 항목의 역할이 헷갈리는 분을 위한 글입니다. 로컬 리스닝, 직접 연결, 차단만 포함한 완성형 설정으로 요청 흐름을 살펴본 뒤 포트, tag, 규칙 순서를 설명합니다. 마지막으로 실제 프록시 아웃바운드를 연결하는 방법과 v2rayN이 임시 생성한 파일을 수정했다가 내용이 덮어써지는 문제도 다룹니다.

1. config.json의 요청 흐름

config.json은 코어가 읽는 설정 파일로, 구독 링크나 클라이언트 설정 파일과는 다릅니다. 구독에는 보통 노드 정보가 들어 있고, 클라이언트는 노드와 라우팅, 로컬 리스닝 설정을 바탕으로 코어 설정을 생성합니다. 이 관계를 이해해야 수정할 내용을 노드 편집, 라우팅 설정, 사용자 지정 설정 중 어디에 넣을지 판단할 수 있습니다.

앱 요청 인바운드 수신 라우팅 규칙 매칭 아웃바운드 연결 대상 서버

inbounds는 요청이 어디로 들어오는지, outbounds는 요청이 어떻게 나가는지, routing은 요청에 어떤 출구를 사용할지 정합니다. 브라우저가 먼저 로컬 프록시 포트로 요청을 보내야 라우팅 규칙이 적용됩니다. 라우팅만 수정해도 모든 앱이 자동으로 프록시를 사용하는 것은 아닙니다.

이 글에서는 V2Ray에서 흔히 사용하는 JSON 설정 구조를 다룹니다. V2Fly와 Xray는 공통 필드가 있지만, 프로토콜 기능과 필드 옵션, 명령줄 인수는 실제 사용하는 코어에 따라 확인해야 합니다. v2rayNG는 Xray 코어를, v2flyNG는 V2Fly 코어를 사용합니다. 데스크톱용 v2rayN은 이름만 보고 판단하지 말고 현재 설정에서 실제로 어떤 코어를 호출하는지 먼저 확인하세요.

2. 완성형 설정 예시: 인바운드 2개, 직접 연결, 차단

아래는 구조를 모두 갖춘 학습용 설정입니다. SOCKS는 127.0.0.1:10808, HTTP는 127.0.0.1:10809에서 리슨합니다. 예시 도메인 example.org와 하위 도메인으로 향하는 요청은 차단하고, 나머지는 기본적으로 직접 연결합니다. 원격 프록시 노드는 포함되어 있지 않으므로 원격 프록시에 사용할 수 있는 노드 설정은 아닙니다.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    },
    {
      "tag": "http-in",
      "listen": "127.0.0.1",
      "port": 10809,
      "protocol": "http",
      "settings": {}
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "blocked",
      "protocol": "blackhole",
      "settings": {}
    }
  ],
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "domain": [
          "domain:example.org"
        ],
        "outboundTag": "blocked"
      },
      {
        "type": "field",
        "ip": [
          "10.0.0.0/8",
          "172.16.0.0/12",
          "192.168.0.0/16",
          "127.0.0.0/8",
          "::1/128"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "inboundTag": [
          "http-in"
        ],
        "outboundTag": "direct"
      }
    ]
  }
}

엄격한 JSON 형식입니다. 키와 문자열은 큰따옴표로 감싸고, 주석이나 마지막 쉼표는 사용할 수 없습니다. 배열에는 여러 객체를 넣을 수 있지만 각 객체는 해당 프로토콜의 필드 정의를 따라야 합니다. 화면에 표시되는 ‘노드 이름’을 코어 설정의 키로 사용하지 마세요. 최상위 항목에 subscription 필드를 임의로 추가해서도 안 됩니다. 구독 관리는 클라이언트 기능입니다.

3. inbounds: 리스닝 주소, 포트, 인바운드 tag

listen은 로컬에서 바인딩할 주소이지 원격 노드 주소가 아닙니다. port는 앱이 연결하는 로컬 포트이지 서버 포트가 아닙니다. 리스닝 주소를 127.0.0.1로 설정하면 로컬 IPv4 루프백 연결만 허용합니다. 모든 네트워크 인터페이스에서 리슨하도록 바꾸면 접근 범위가 넓어지므로, 이는 연결 실패를 해결하는 일반적인 방법이 아니며 로컬 네트워크 접근 제어와 관련이 있습니다.

SOCKS 인바운드

protocol
socks
listen
127.0.0.1
port
10808
tag
socks-in

앱이 SOCKS 프로토콜을 사용해 연결해야 합니다. UDP 사용 여부는 앱과 이후 아웃바운드 경로에도 달려 있습니다.

HTTP 인바운드

protocol
http
listen
127.0.0.1
port
10809
tag
http-in

HTTPS 대상에는 보통 CONNECT 터널을 사용합니다. 인바운드 프로토콜을 https로 바꿀 필요는 없습니다.

tag는 설정 내부에서 사용하는 식별자입니다. 인바운드의 socks-in은 라우팅 규칙의 inboundTag에서 참조할 수 있습니다. tag는 프로토콜을 결정하지 않으며 같은 이름의 아웃바운드를 자동으로 만들지도 않습니다. 인바운드와 아웃바운드마다 고유하고 안정적인 tag를 지정하고, 이름을 바꿀 때는 해당 tag를 참조하는 모든 항목도 함께 확인하세요.

예시의 udp: true는 SOCKS 인바운드에서 UDP를 허용한다는 뜻입니다. 앱이 SOCKS UDP ASSOCIATE를 지원해야 하며 아웃바운드도 해당 트래픽을 처리할 수 있어야 합니다. 이 옵션을 켠다고 시스템의 모든 UDP 트래픽이 코어를 통과하는 것은 아닙니다. 일반 HTTP 시스템 프록시로 게임 트래픽을 가로챌 수 있다는 뜻도 아닙니다.

4. outbounds: 프로토콜 구현과 기본 출구

예시에서 freedom은 직접 연결, blackhole은 차단을 뜻합니다. direct와 blocked는 임의로 지정한 tag일 뿐입니다. direct를 office-direct로 바꿔도 동작은 달라지지 않지만, 이를 참조하는 모든 outboundTag도 함께 수정해야 합니다.

필드 또는 위치예시 값실제 역할
protocolfreedom직접 연결에 사용할 프로토콜 구현
tagdirect라우팅 규칙에서 참조하는 식별자
settings{}해당 아웃바운드 프로토콜의 설정 객체
배열의 첫 번째 항목direct어떤 규칙에도 일치하지 않을 때 사용할 기본 아웃바운드

일반 라우팅 규칙에 일치하는 항목이 없으면 코어는 outbounds 배열의 첫 번째 항목을 사용합니다. 예시에서는 직접 연결이 첫 번째이므로 차단 규칙이나 특정 인바운드 규칙에 걸리지 않은 트래픽은 직접 연결됩니다. 차단 아웃바운드를 첫 번째로 옮기면 매칭되지 않은 요청 대부분이 차단됩니다. 이는 ‘노드가 갑자기 작동하지 않는’ 문제가 아니라 기본 출구가 바뀐 것입니다.

확인 순서: 기본 출구부터 확인한 뒤 예외 규칙을 살펴보세요

설정을 점검할 때는 먼저 outbounds[0].tag를 확인한 다음 routing.rules를 하나씩 살펴보세요. 그러면 매칭되지 않은 트래픽이 어디로 가는지 먼저 파악할 수 있어 도메인 규칙 하나에만 집중하다 전체 동작을 놓치는 일을 막을 수 있습니다.

5. routing: 순차 매칭, 조건 조합, 도메인 확인

routing.rules는 배열 순서대로 위에서 아래로 확인합니다. 처음 일치하는 규칙이 출구를 결정하며, 이후 규칙 중 ‘더 정확한’ 항목을 계속 찾지 않습니다. 구체적인 예외 규칙을 앞에, 적용 범위가 넓은 규칙을 뒤에 두는 것이 일반적입니다. 첫 규칙이 모든 TCP와 UDP를 매칭하는 출구 규칙이면 뒤에 있는 도메인 규칙은 적용되지 않습니다.

  1. 먼저 대상 도메인을 확인합니다. 요청 대상이 example.org 또는 그 하위 도메인이면 첫 번째 규칙에 따라 blocked를 선택합니다. domain:은 해당 도메인과 하위 도메인에 매칭됩니다. 도메인 하나만 정확히 일치시키려면 full:을 사용할 수 있습니다.
  2. 다음으로 대상 IP를 확인합니다. 예시에 나온 사설 주소와 루프백 주소는 direct로 연결됩니다. 이 규칙은 주소 대역에 매칭되며, 도메인이 사설 주소로 해석될 가능성만으로 반드시 적용되는 것은 아닙니다.
  3. 그다음 요청이 들어온 인바운드를 확인합니다. 세 번째 규칙은 http-in에서 들어온 요청에 매칭되며 직접 연결을 선택합니다. 앞선 도메인 차단 규칙에 이미 걸린 요청에는 이 규칙이 적용되지 않습니다.
  4. 마지막으로 기본 아웃바운드를 사용합니다. socks-in에서 들어오고 앞의 두 규칙에 매칭되지 않은 요청은 배열의 첫 번째 항목인 direct를 사용합니다.

하나의 규칙 안에서는 서로 다른 조건 필드가 모두 충족되어야 하는 경우가 많습니다. 예를 들어 inboundTag와 domain을 함께 지정하면 특정 인바운드로 들어온 특정 도메인의 요청을 뜻합니다. 같은 도메인 배열에 여러 항목을 넣으면 그중 하나에 일치하는 요청이 대상입니다. ‘또는’ 조건을 여러 필드를 포함한 하나의 규칙으로 잘못 작성하는 것은 트래픽 분기가 적용되지 않는 흔한 원인입니다.

domainStrategy는 routing 안에 위치하며 라우팅 매칭 단계에서 도메인을 처리하는 방식을 제어합니다. AsIs는 IP 규칙에 매칭하기 위해 도메인을 먼저 해석하지 않습니다. IPIfNonMatch는 도메인이 매칭되지 않으면 해석을 시도한 뒤 다시 매칭합니다. IPOnDemand는 IP 정보가 필요한 규칙을 확인할 때 도메인 해석을 요청합니다. 이 설정은 ‘모든 DNS 요청을 프록시로 보내는’ 옵션이 아닙니다.

6. 프록시 아웃바운드 연결: 업스트림을 확인한 뒤 참조를 수정하세요

원격 프록시를 사용하려면 실제 서버 매개변수가 필요합니다. VMess, VLESS 등의 아웃바운드는 서버 주소, 포트, 인증 정보, 전송 및 보안 설정이 서버와 일치해야 합니다. protocol 이름만 바꾼다고 프로토콜이 변환되는 것은 아닙니다. 기존 노드는 클라이언트로 가져온 뒤 생성된 설정을 확인하는 편이 좋습니다. 서로 다른 코어의 프로토콜 필드를 직접 조합하지 마세요.

‘아웃바운드를 추가한 뒤 참조하는 방법’을 따로 보여주기 위해 여기서는 로컬 SOCKS 업스트림을 사용합니다. 다른 독립 서비스가 127.0.0.1:10810에서 실제로 리슨하고 인증을 요구하지 않는다는 전제입니다. 이 객체는 outbounds 배열에 추가해야 합니다. 전체 설정 파일이 아니라 설정 일부입니다.

{
  "tag": "proxy",
  "protocol": "socks",
  "settings": {
    "servers": [
      {
        "address": "127.0.0.1",
        "port": 10810
      }
    ]
  }
}

7. 설정 검증과 v2rayN의 저장 위치

검증은 세 단계로 나뉩니다. JSON을 파싱할 수 있는지, 코어가 필드를 허용하는지, 실제 요청이 규칙대로 처리되는지 확인해야 합니다. 한 단계로 다른 단계를 대신할 수는 없습니다. JSON 문법이 올바르더라도 참조한 tag가 존재한다는 보장은 없으며, 코어가 정상적으로 시작되어도 시스템 프록시가 올바른 포트를 가리킨다는 뜻은 아닙니다.

  1. 원본 파일을 백업하세요. 정상적으로 시작되는 복사본을 보관하고, 매번 포트나 라우팅처럼 한 가지 항목만 수정하면 문제가 생겼을 때 쉽게 되돌릴 수 있습니다.
  2. 실행할 코어에 맞춰 검사하세요. 먼저 패키지에 포함된 코어의 도움말 명령을 실행해 해당 버전에서 지원하는 설정 테스트 옵션을 확인하세요. 검사 도구는 실제 실행에 사용하는 코어와 일치해야 합니다. 다른 코어의 검사 결과로 대신 판단하지 마세요.
  3. 시작 로그를 확인하세요. 리스닝 실패 메시지가 나오면 주소나 포트를 먼저 확인하고, 설정 로드 실패 메시지가 나오면 필드 유형과 프로토콜 설정을 확인하세요. 문제를 찾는 동안에는 loglevel을 info로 잠시 바꾼 뒤 확인이 끝나면 원래대로 돌리세요.
  4. 목적이 분명한 요청 두 가지를 보내세요. 먼저 직접 연결 출구를 확인하고, 다음으로 차단 규칙을 확인합니다. 테스트할 때 포트를 명시하면 다른 앱의 프록시 설정이 결과에 섞이지 않습니다.
curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 5 https://example.com/
curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 5 https://example.org/
curl --proxy http://127.0.0.1:10809 --connect-timeout 5 https://example.com/

socks5h는 대상 호스트 이름을 프록시에서 처리하도록 요청하므로 이 예시의 도메인 규칙을 확인하는 데 적합합니다. 외부 네트워크에 연결할 수 있다면 첫 번째와 세 번째 요청은 직접 연결을 시도하고 두 번째 요청은 차단되어야 합니다. 구체적인 오류 메시지는 코어와 curl 버전에 따라 다를 수 있습니다. 특정 오류 코드만으로 어떤 규칙에 걸렸는지 판단하지 말고 로그도 함께 확인하세요. Windows에서 터미널 별칭이 명령을 가로채면 curl.exe를 직접 실행하세요.

v2rayN에서는 먼저 ‘설정’ → ‘매개변수 설정’에서 로컬 리스닝 포트를 확인한 뒤, ‘설정’ → ‘라우팅 설정’에서 활성화된 라우팅 설정을 확인하세요. 버전에 따라 메뉴 그룹이나 이름이 조금 다를 수 있으므로 현재 화면을 기준으로 확인하면 됩니다. 일반 노드를 실행할 때 사용하는 설정은 클라이언트가 다시 생성하는 경우가 많습니다. 노드 전환, 구독 업데이트, 코어 재시작 후에는 생성된 파일을 직접 수정한 내용이 덮어써질 수 있습니다.

설정 저장 위치: 자주 쓰는 규칙은 클라이언트 설정에 저장하세요

포트와 트래픽 분기 규칙을 계속 유지하려면 클라이언트의 해당 설정을 수정하세요. JSON 전체를 직접 관리하려면 클라이언트의 사용자 지정 설정 기능을 사용하고, 해당 설정 항목에서 어떤 코어를 호출하는지 확인해야 합니다. 화면 설정과 임시 파일을 따로 관리하면서 두 설정이 자동으로 합쳐질 것이라고 기대하지 마세요.

8. 자주 묻는 설정 문제

노드를 선택했는데 왜 예시 설정은 직접 연결되나요?

이 예시의 첫 번째 아웃바운드는 freedom이며 원격 프록시 매개변수가 없습니다. 클라이언트에서 노드를 선택해도 별도로 실행하는 JSON 설정이 자동으로 바뀌지는 않습니다. 실제로 어떤 파일을 불러왔는지 확인한 다음 outbounds[0]과 매칭된 규칙의 outboundTag를 점검하세요.

tag 이름을 바꾼 뒤 규칙이 작동하지 않는 이유는 무엇인가요?

파일에서 이전 tag를 검색해 outboundTag와 inboundTag를 하나씩 확인하세요. 참조 값은 대상 객체의 tag와 대소문자까지 완전히 일치해야 합니다. 수정 후 다시 테스트하고 코어를 재시작하세요. 텍스트만 저장한 채 이전 프로세스를 계속 사용하면 안 됩니다.

시스템 프록시를 켰는데 일부 앱이 왜 10809를 사용하지 않나요?

시스템 프록시는 해당 설정을 따르는 앱에서만 작동합니다. 먼저 위의 HTTP 테스트 명령으로 리스닝을 확인한 뒤 앱 자체에 별도 프록시 설정이 있는지 살펴보세요. 앱이 프록시를 사용하지 않는 문제를 해결하려고 리스닝 주소 범위를 넓히지 마세요. 서로 다른 계층의 문제입니다.

구독 내용을 config.json에 그대로 붙여넣어도 되나요?

임의의 구독 텍스트를 코어용 JSON으로 사용할 수는 없습니다. 먼저 v2rayN의 구독 관리에서 가져와 업데이트한 뒤 노드 프로토콜이 선택한 코어에서 지원되는지 확인하세요. 설정 구조를 살펴보려면 클라이언트가 생성한 파일을 확인하고, 공유하기 전에 주소와 인증 정보는 반드시 가리세요.

각 필드의 정의를 더 살펴볼 때는 JSON 매뉴얼에서 인바운드, 아웃바운드, 라우팅 항목을 확인할 수 있습니다. 실제 문제를 점검할 때도 앱이 연결되는지, 인바운드가 리슨하는지, 규칙이 매칭되는지, 아웃바운드가 연결되는지 순서대로 확인하세요. DNS, 포트, 프로토콜 매개변수를 한꺼번에 바꾸기보다 한 계층씩 검증하면 원인을 찾기 쉽습니다.

v2rayN 다운로드