V2Ray config.json 구조 해설: inbounds·outbounds·routing 이해하기

인바운드 리스너부터 아웃바운드 태그와 라우팅 규칙 순서까지, V2Ray 설정 파일의 JSON 필드를 살펴보고 클라이언트가 생성한 설정을 이해합니다.

이 글 미리보기

구독을 가져온 뒤 config.json을 더 깊이 살펴보고 싶은 Mac 사용자를 위한 글입니다. 먼저 로컬 인바운드, 직접 연결, 차단만 포함한 전체 설정으로 구조를 파악한 다음 포트, 아웃바운드 태그, 규칙 순서와 DNS를 확인합니다. 예시 포트는 설명을 위해 지정한 값이며 모든 클라이언트의 기본 설정을 의미하지 않습니다.

config.json과 v2rayN 설정의 차이

config.json은 코어가 읽는 설정 파일이고, v2rayN은 노드, 구독, 실행 상태를 관리하는 데스크톱 클라이언트입니다. 두 항목은 서로 다른 계층에 있습니다. 클라이언트는 인터페이스 설정을 코어 실행 설정으로 변환하거나 사용자 지정 설정을 관리할 수 있습니다. 파일 이름이 config.json이라는 이유만으로 해당 파일을 현재 프로세스가 읽고 있다고 단정할 수는 없습니다.

이 글에서는 V2Ray의 기존 JSON 설정 구조를 사용합니다. Xray와 V2Ray는 비슷한 필드가 있지만 프로토콜 지원과 세부 매개변수는 완전히 같지 않습니다. 설정을 살펴보기 전에 클라이언트 실행 로그에서 실제로 시작된 코어, 설정 파일 경로, 시작 오류를 확인하세요. 한 코어에 맞는 프로토콜 매개변수를 다른 코어에 그대로 붙여 넣지 마세요.

앱 요청 로컬 인바운드 라우팅 규칙 확인 대상 아웃바운드 원격 서버

이 흐름을 보면 시스템 프록시의 역할도 알 수 있습니다. 시스템 프록시 설정을 따르는 앱은 요청을 로컬 인바운드로 보냅니다. 코어만 단독으로 실행한다고 모든 앱이 자동으로 이 경로를 이용하는 것은 아닙니다. 시스템 프록시를 사용하지 않는 앱에는 해당 앱의 프록시 옵션이나 트래픽을 명시적으로 전달하는 다른 방법이 필요합니다.

전체 예시: 인바운드 2개, 아웃바운드 2개

아래는 로컬에서 설정 구조를 파악하기 위한 예시입니다. SOCKS와 HTTP 진입점을 제공하고, 기본적으로 직접 연결하며, 예시 도메인 example.com과 하위 도메인을 차단합니다. 원격 프록시 서버는 설정되어 있지 않으므로 구독 노드 설정으로 사용할 수 없습니다. 테스트 전에 실행 중인 클라이언트가 이 두 포트를 사용하고 있지 않은지 확인하세요.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "local-socks",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": false
      }
    },
    {
      "tag": "local-http",
      "listen": "127.0.0.1",
      "port": 10809,
      "protocol": "http",
      "settings": {}
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "blocked",
      "protocol": "blackhole",
      "settings": {}
    }
  ],
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "domain": [
          "domain:example.com"
        ],
        "outboundTag": "blocked"
      },
      {
        "type": "field",
        "ip": [
          "10.0.0.0/8",
          "172.16.0.0/12",
          "192.168.0.0/16"
        ],
        "outboundTag": "direct"
      }
    ]
  }
}

가장 바깥쪽 구조를 보면 인바운드, 아웃바운드, 규칙은 모두 배열입니다. 대괄호 안에 객체를 여러 개 넣을 수 있으며, 객체 사이는 쉼표로 구분하고 마지막 항목 뒤에는 쉼표를 붙이지 않습니다. 일반적인 JSON 도구와 호환되도록 저장할 때는 큰따옴표를 사용하고 주석은 넣지 마세요. 포트도 따옴표로 감싼 문자열이 아니라 숫자로 작성해야 합니다.

10808
예시 SOCKS 인바운드 포트
10809
예시 HTTP 인바운드 포트
2개
순서대로 확인하는 라우팅 규칙

inbounds: 리스닝 주소, 포트, 인바운드 태그

listen은 연결을 받을 네트워크 인터페이스를 지정합니다. 예시의 127.0.0.1은 로컬 기기에서만 연결할 수 있습니다. 이를 0.0.0.0으로 바꾸면 모든 IPv4 인터페이스에서 리슨하지만, 다른 기기에서 반드시 접속할 수 있다는 뜻은 아닙니다. 방화벽, 네트워크 격리, 인증도 고려해야 합니다. 로컬에서만 사용할 때는 루프백 주소를 유지하는 편이 접근 범위를 제어하기 쉽습니다.

SOCKS 인바운드

태그
local-socks
리스닝 주소
127.0.0.1:10808
프로토콜
socks
인증
noauth, 로컬에서만 사용
UDP
false, 이 예시에서는 비활성화

앱에서 SOCKS 프록시를 선택해야 합니다. 이 포트를 HTTP 프록시 포트로 입력하면 안 됩니다.

HTTP 인바운드

태그
local-http
리스닝 주소
127.0.0.1:10809
프로토콜
http
매개변수
settings: {}

HTTP 프록시 요청과 CONNECT 터널 요청을 받을 수 있습니다. 이 진입점 자체가 TLS 암호화 리스너를 의미하지는 않습니다.

macOS에서는 ‘시스템 설정’ → ‘네트워크’ → 현재 네트워크 연결 → ‘세부사항’ → ‘프록시’에서 주소와 포트를 확인할 수 있습니다. 이 예시의 HTTP 인바운드를 사용한다면 필요에 따라 웹 프록시와 보안 웹 프록시에 127.0.0.1:10809을 지정할 수 있습니다. SOCKS 인바운드의 포트는 10808입니다. 문제를 진단하는 동안에는 필요한 방식 하나만 활성화해 설정 간 간섭을 피하세요.

tag는 코어 설정 내부에서 사용하는 식별자이며, 서버 주소도 아니고 모든 앱이 알아보는 이름도 아닙니다. 라우팅의 inboundTag로 요청의 출처를 구분할 수 있습니다. 예를 들어 한 규칙은 local-http만 처리하고 다른 규칙은 local-socks만 처리하도록 설정하면 진입점별로 정책을 지정할 수 있습니다.

outbounds: 프로토콜이 동작을 결정하고 tag가 라우팅을 연결합니다

예시의 freedom은 직접 연결 아웃바운드로, 현재 기기에서 대상에 직접 접속합니다. blackhole은 요청을 차단합니다. 실제 원격 프록시 아웃바운드에는 해당 프로토콜에 맞는 서버 주소, 포트, 인증 정보와 필요한 전송 계층 설정이 있어야 합니다. tag만 proxy로 바꿔도 직접 연결이 프록시 연결로 바뀌지는 않습니다.

필드 또는 프로토콜의미확인할 사항
tag아웃바운드 내부 이름규칙의 outboundTag와 정확히 일치하는지
protocol아웃바운드 구현 선택실행 중인 코어가 해당 프로토콜을 지원하는지 확인
settings해당 프로토콜 전용 매개변수프로토콜마다 구조가 다르므로 같은 형식을 적용할 수 없음
streamSettings전송 방식과 전송 보안 설정서버 요구 사항과 항목별로 일치하는지
freedom대상에 직접 연결이 기기의 실제 네트워크 출구를 사용하는지
blackhole조건에 맞는 요청 차단정상적인 트래픽이 잘못 매칭되지 않는지 확인

일반적인 설정에서는 라우팅 규칙에 일치하지 않는 요청에 outbounds 배열의 첫 번째 아웃바운드가 사용됩니다. 이 예시에서는 direct가 첫 번째 항목이므로 규칙에 일치하지 않는 요청은 직접 연결됩니다. 클라이언트가 생성한 파일을 읽을 때 이 순서는 특히 중요합니다. 파일에 프록시 아웃바운드가 있다고 해서 규칙에 일치하지 않는 트래픽도 반드시 그 아웃바운드를 사용하는 것은 아닙니다.

핵심: 기본 아웃바운드를 먼저 확인한 다음 개별 규칙을 살펴보세요

‘왜 여전히 직접 연결되는지’ 확인할 때는 먼저 outbounds의 첫 번째 항목을 살펴보고, 규칙에 일치한다면 outboundTag도 확인하세요. 노드 이름이나 인터페이스 색상만으로 실제 네트워크 출구를 판단하지 마세요.

routing: 첫 번째 일치 규칙, 조건 조합, 도메인 정책

routing.rules는 위에서 아래 순서로 확인합니다. 규칙 하나가 일치하면 지정된 아웃바운드를 사용하고, 더 구체적인 규칙을 찾기 위해 아래로 계속 내려가지 않습니다. 따라서 명확한 예외 규칙은 보통 범위가 넓은 규칙보다 앞에 둡니다. 배열 순서는 설정 동작의 일부이므로 순서를 바꾸면 문법 오류 없이도 네트워크 출구가 달라질 수 있습니다.

  1. 요청 대상이 www.example.com인 경우: 첫 번째 규칙의 domain:example.com이 기본 도메인과 하위 도메인에 일치하므로 blocked로 전달됩니다.
  2. 요청 대상이 192.168.1.20인 경우: 첫 번째 규칙에는 일치하지 않지만 두 번째 규칙의 사설 IP 주소 대역에는 일치하므로 direct로 전달됩니다.
  3. 대상이 위 조건에 해당하지 않는 경우: 어떤 규칙에도 일치하지 않으므로 첫 번째 아웃바운드인 direct를 사용합니다.

하나의 규칙에서 서로 다른 조건 필드는 보통 모두 충족해야 합니다. 같은 필드의 후보 값은 어느 하나에 일치하면 됩니다. 예를 들어 아래 규칙은 HTTP 인바운드에서 들어오고 대상 포트가 25인 TCP 요청만 차단합니다. SOCKS 인바운드로 들어온 요청은 포트가 같더라도 모든 조건을 충족하지 않습니다.

{
  "type": "field",
  "inboundTag": ["local-http"],
  "network": "tcp",
  "port": "25",
  "outboundTag": "blocked"
}

이 객체는 기존 routing.rules 배열에 넣고 필요한 위치에 배치해야 합니다. 전체 설정은 아닙니다. 필드 유형도 다르다는 점에 유의하세요. 인바운드 리스너의 port는 숫자지만 라우팅 규칙의 port는 포트나 범위를 문자열로 지정합니다. 예: "80,443".

geosite: 또는 geoip: 유형의 조건을 사용할 때는 코어가 해당 데이터 파일을 읽을 수 있는지, 지정한 항목 이름이 존재하는지 확인해야 합니다. 이 글에서는 외부 데이터 의존성을 배제하고 규칙 동작을 확인할 수 있도록 구체적인 도메인과 CIDR 대역을 사용합니다. routing.domainStrategy를 직접 연결 아웃바운드의 이름이 같거나 비슷한 DNS 정책 옵션과 혼동하지 마세요.

DNS와 sniffing: 규칙에서 확인할 수 있는 대상 정보

도메인 규칙이 일치하는지는 인바운드에서 어떤 대상 정보를 받는지에 따라 달라집니다. HTTP 프록시 요청과 CONNECT에는 보통 도메인이 포함됩니다. SOCKS 요청에는 도메인이 전달될 수도 있고, 앱이 미리 조회한 IP 주소가 전달될 수도 있습니다. 앱이 로컬에서 먼저 도메인을 조회하면 코어에는 주소만 전달될 수 있으므로, 코어가 원래 도메인을 알고 있다고 단정할 수 없습니다.

curl --proxy socks5h://127.0.0.1:10808 --connect-timeout 5 https://example.com/
curl --proxy http://127.0.0.1:10809 --connect-timeout 5 https://example.com/

첫 번째 명령의 socks5h는 curl이 도메인 조회를 SOCKS 프록시에 맡기도록 합니다. socks5를 사용하면 curl이 로컬에서 대상 도메인을 조회합니다. 이 예시에서는 해당 도메인 요청이 차단되어야 하므로 명령이 실패하는 것이 예상된 결과입니다. 구체적인 오류 메시지는 연결 단계에 따라 달라집니다. 여기서 5는 연결 제한 시간(초)이며 성능 측정값이 아닙니다.

설정 변경 순서와 자주 발생하는 오류 확인 방법

먼저 백업하고 현재 적용 중인 파일을 확인한 다음 수정하세요. v2rayN은 노드 전환, 구독 업데이트, 코어 재시작 시 실행 설정을 다시 생성할 수 있습니다. 생성된 파일을 임시로 수정하는 방법은 문제를 확인할 때는 쓸 수 있지만 규칙을 장기간 보존하기에는 적합하지 않습니다. 유지해야 하는 변경 사항은 클라이언트의 해당 설정이나 관리 대상 사용자 지정 설정에 반영하세요.

  1. 코어와 경로를 확인합니다. 시작 로그에서 코어 이름, 불러온 파일, 오류가 발생한 줄을 확인하세요. 경로에 공백이 있으면 터미널 명령에서 파일 경로를 따옴표로 감싸야 합니다.
  2. JSON 구조를 확인합니다. macOS 터미널에서 아래 명령을 실행하세요. 이 명령은 JSON을 파싱할 뿐, 코어가 각 필드를 지원하는지는 검증하지 않습니다.
  3. 리스닝 포트를 확인합니다. 10808과 10809를 다른 프로세스가 사용 중인지 확인하고 앱 설정에 지정된 포트와 일치하는지 살펴보세요.
  4. 태그 참조를 확인합니다. outboundTag가 실제로 존재하는 아웃바운드를 가리키는지, inboundTag가 인바운드 태그와 일치하는지 하나씩 확인하세요.
  5. 다시 불러오고 검증합니다. 클라이언트에서 코어를 재시작하거나 설정을 다시 불러온 뒤 정상적으로 시작되는지 확인하세요. 그런 다음 규칙에 일치하는 대상과 일치하지 않는 대상을 각각 테스트합니다.
plutil -lint -s -r -- config.json
lsof -nP -iTCP:10808 -sTCP:LISTEN
lsof -nP -iTCP:10809 -sTCP:LISTEN

plutil은 여기서 JSON 문법을 확인하는 데 사용합니다. 문법 검사를 통과해도 프로토콜 이름의 오타, 필수 매개변수 누락, 코어 비호환성으로 인해 시작에 실패할 수 있습니다. 리스너 확인 결과가 비어 있다고 해서 설정 문법 오류라고 단정할 수도 없습니다. 프로세스가 시작되었는지, 다른 파일을 읽고 있지는 않은지도 확인하세요.

증상우선 확인할 항목다음 조치
코어 시작 직후 종료JSON 문법, 필드 호환성, 필수 매개변수시작 로그에서 처음 발생한 실제 오류 확인
주소가 이미 사용 중이라는 메시지인바운드 포트와 중복 실행 프로세스포트를 점유한 프로세스를 찾아 중지하거나 포트 변경
규칙이 적용되지 않음규칙 순서, 대상 형식, 인바운드 태그고정된 프록시 명령으로 재현하고 로그와 비교
재시작 후 변경 사항이 사라짐생성된 파일을 수정했는지 확인설정을 클라이언트의 관리 메뉴로 옮기기
브라우저는 되지만 다른 앱은 실패앱이 시스템 프록시를 따르는지 확인앱 자체의 프록시 유형과 포트 확인

핵심: 한 번에 한 계층씩 수정하세요

먼저 인바운드가 요청을 받는지 확인하고, 다음으로 아웃바운드가 작동하는지 확인한 뒤 라우팅을 조정하세요. 문제를 진단하는 동안에만 로그 상세도를 잠시 높이고, 작업을 마치면 원래 수준으로 되돌리세요. 로그와 설정을 공유하기 전에는 구독 주소, 인증 정보, 개인 대상 정보를 삭제하세요.

v2rayN 다운로드플랫폼에 맞는 클라이언트 선택