V2Ray config.json 설정 구조와 해설: inbounds·outbounds·routing

config.json의 log, inbounds, outbounds, routing, dns를 순서대로 살펴보고 각 필드의 역할과 자주 쓰는 설정을 설명합니다.

이 글의 핵심

구독을 가져온 경험이 있고, 코어가 사용하는 실행 설정을 이해하고 싶은 사용자를 위한 글입니다. 먼저 앱의 진입점, 연결의 출구, 라우팅 선택을 구분한 다음 로컬 리스닝·직접 연결·차단만 포함한 전체 설정으로 필드 간 관계를 확인합니다. 포트 충돌, 잘못된 태그, 규칙 순서 오류, DNS 설정이 적용되지 않는 원인을 파악할 수 있습니다.

먼저 구분하기: config.json은 코어 설정 파일이지 구독 목록이 아닙니다

v2rayN은 데스크톱 클라이언트이고, V2Ray와 Xray는 연결을 처리하는 코어입니다. 클라이언트는 구독, 노드, 인터페이스 설정을 저장한 뒤 선택한 코어에 맞춰 실행 설정을 생성합니다. 따라서 구독에 포함된 VMess 또는 VLESS 공유 링크 하나가 완전한 config.json인 것은 아닙니다. 보통 원격 연결 정보만 담고 있으며 로컬 인바운드, 로그, DNS, 라우팅 규칙은 포함하지 않습니다.

이 글에서는 V2Ray의 기존 JSON 설정 형식을 사용해 V2Ray와 Xray에서 흔히 볼 수 있는 필드 간 관계를 설명합니다. V2Ray v5에는 별도의 설정 형식도 있으므로 파일 확장자만으로 형식을 판단하거나 두 형식의 필드를 섞어서는 안 됩니다. 프로토콜 및 전송 관련 필드는 실제로 실행하는 코어 버전을 기준으로 확인하세요.

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

문제를 찾을 때는 이 흐름을 따라가세요. 웹 요청이 라우팅에 전달되려면 먼저 인바운드로 들어와야 합니다. 라우팅이 선택하는 것은 아웃바운드 태그이지 구독 그룹 이름이 아닙니다. DNS는 도메인 해석과 일부 라우팅 판단을 돕지만, 모든 트래픽이 반드시 거치는 '프록시 출구'는 아닙니다.

log: 확인할 수 있는 정보를 먼저 정한 뒤 연결 매개변수 조정하기

log는 최상위 필드이며 로그 출력을 제어합니다. 문제를 진단할 때는 먼저 info를 사용하고, 해결 후에는 보통 warning으로 되돌릴 수 있습니다. 처음부터 debug를 켜면 세부 로그가 지나치게 많아져 간단한 포트 충돌 메시지를 찾기 어려울 수 있습니다.

{
  "log": {
    "loglevel": "info"
  }
}

이 코드는 필드 예시이며 완전한 실행 설정은 아닙니다. 자주 쓰는 로그 수준은 debug, info, warning, error, none입니다. 여기서는 로그 파일 경로를 설정하지 않아 터미널이나 클라이언트 로그 창에서 시작 결과를 확인합니다. access 및 error 파일을 지정한다면 대상 디렉터리가 있고 실행 계정에 쓰기 권한이 있는지 확인하세요.

inbounds: 앱은 어떤 주소와 포트로 연결되는가

inbounds는 배열이므로 하나의 설정에서 SOCKS와 HTTP 프록시 요청을 모두 받을 수 있습니다. listen은 수신 대기 주소, port는 로컬 포트, protocol은 앱이 연결할 프록시 프로토콜을 지정합니다. 원격 노드의 주소와 포트가 아닙니다.

10808
이 글에서 사용하는 SOCKS 인바운드 포트
10809
이 글에서 사용하는 HTTP 인바운드 포트
127.0.0.1
로컬 기기에서만 접속할 수 있는 리스닝 주소

여기서 지정한 포트는 이 글의 예시에 사용한 값이며 모든 클라이언트 버전의 기본값을 뜻하지 않습니다. v2rayN이 해당 포트를 이미 사용 중이면 테스트 코어가 같은 포트를 다시 열 수 없습니다. 포트를 점유한 프로세스를 먼저 종료하거나 설정과 테스트 명령의 포트를 모두 바꾸세요. 한쪽만 바꾸면 안 됩니다.

SOCKS 인바운드

tag
local-socks
listen
127.0.0.1
port
10808
protocol
socks
settings
auth는 noauth, udp는 false

이 예시에서는 TCP만 테스트합니다. UDP 전달을 끄더라도 일반 HTTPS 연결에는 지장이 없습니다.

HTTP 인바운드

tag
local-http
listen
127.0.0.1
port
10809
protocol
http
settings
빈 객체이며 해당 인바운드의 기본 설정을 사용합니다

HTTPS 웹사이트는 일반적으로 HTTP 프록시의 CONNECT 메서드로 터널을 설정합니다.

로컬 앱의 HTTP 프록시를 127.0.0.1:10809로 설정하거나 SOCKS5 프록시를 127.0.0.1:10808로 설정하세요. 포트 번호만 보고 두 프로토콜을 임의로 바꿔 쓸 수는 없습니다. 리스닝 주소를 0.0.0.0으로 바꾸면 접속 가능한 범위가 넓어집니다. 접근 제어와 방화벽 제한이 없다면 인증 없는 프록시를 다른 기기에서 접속할 수 있는 네트워크에 노출하지 마세요.

outbounds: 출구 태그와 출구 프로토콜은 다릅니다

outbounds도 배열입니다. tag는 설정 내부에서 참조하는 이름이고 protocol은 실제 아웃바운드 방식입니다. 태그를 proxy라고 지정해도 자동으로 프록시 기능이 생기지는 않습니다. 해당 항목의 프로토콜이 freedom이면 여전히 직접 연결입니다.

{
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "block",
      "protocol": "blackhole",
      "settings": {}
    }
  ]
}

이 두 아웃바운드에는 원격 계정이 필요하지 않습니다. freedom은 로컬 네트워크를 통해 대상에 연결하고 blackhole은 일치하는 트래픽을 버립니다. 이 글의 설정에는 로드 밸런서 같은 추가 기능이 없으므로 어떤 라우팅 규칙에도 일치하지 않으면 배열의 첫 번째 아웃바운드를 사용합니다. 따라서 기본 출구는 direct입니다.

요약: 출구를 바꾸기 전에 태그가 가리키는 프로토콜을 확인하세요

규칙이 일치했는데도 로컬 네트워크로 연결되는 문제를 진단할 때는 해당 아웃바운드의 protocol부터 확인하세요. 값이 freedom이라면 도메인 일치 조건을 계속 바꾸기보다 출구 설정을 수정해야 합니다.

routing: 위에서부터 규칙을 확인하고 태그를 찾을 수 있어야 합니다

routing.rules는 순서대로 확인하며 처음 일치하는 규칙이 트래픽의 경로를 결정합니다. 자주 쓰는 조건은 도메인 domain, 대상 IP ip, 포트 port, 인바운드 태그 inboundTag입니다. 한 규칙에 서로 다른 유형의 조건이 있으면 일반적으로 모든 조건을 충족해야 합니다. 같은 도메인 목록의 여러 항목은 후보 일치 항목입니다.

{
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "domain": ["full:blocked.example"],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "ip": ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
        "outboundTag": "direct"
      }
    ]
  }
}

full:blocked.example은 이 도메인과 정확히 일치할 때만 적용됩니다. domain:example.org는 해당 도메인과 하위 도메인에 모두 적용될 수 있습니다. 이 글에서는 실제 사이트에 의존하지 않도록 예시용으로 예약된 도메인으로 차단을 테스트합니다. 규칙의 outboundTag는 정의된 block 및 direct와 각각 일치해야 하며 대소문자도 구분해야 합니다.

domainStrategy일치 동작설정에서 확인할 점
AsIs요청에 이미 포함된 대상 정보로 일치 여부를 판단하며, 라우팅을 위해 도메인을 별도로 조회하지 않습니다도메인 요청이 해석된 IP를 기준으로 한 규칙과 자동으로 일치한다고 볼 수 없습니다
IPIfNonMatch도메인 규칙이 일치하지 않으면 DNS 조회 후 IP 규칙을 확인합니다DNS 조회 결과와 추가로 발생할 수 있는 조회 대기 시간을 확인합니다
IPOnDemand라우팅 규칙에 IP 정보가 필요할 때 조회합니다IP 규칙의 위치에 따라 더 일찍 조회가 발생할 수 있습니다

이 예시에서는 AsIs를 사용하므로 192.168.1.1에 직접 요청하면 사설 IP 규칙과 일치할 수 있습니다. 하지만 사설 주소로 해석되는 도메인에 접속하더라도 이 규칙만으로 라우팅이 DNS 조회를 수행하지는 않습니다. 직접 연결을 설정할 때는 도메인을 조회해야 할 수도 있습니다. 이는 아웃바운드 연결 단계의 작업이며 라우팅 전략과 혼동해서는 안 됩니다.

dns: 내장 DNS가 모든 DNS 요청을 처리하는 것은 아닙니다

최상위 dns 설정은 내장 DNS를 구성합니다. 라우팅에서 도메인에 대응하는 IP가 필요하거나 아웃바운드 설정에서 내장 DNS 결과를 사용하도록 요구할 때 해당 처리에 참여합니다. dns.servers를 작성하는 것만으로 브라우저의 암호화 DNS나 시스템의 모든 DNS 조회를 가로채지는 않으며, 로컬 DNS 리스닝 포트가 자동으로 만들어지지도 않습니다.

{
  "dns": {
    "servers": ["localhost"]
  }
}

여기서 localhost는 시스템의 DNS 조회 기능을 사용하는 특수 값이며, 127.0.0.1:53에서 DNS 서버를 실행해야 한다는 뜻이 아닙니다. 이 예시에서는 설정을 간단하게 하기 위해 사용했지만 DNS 격리 수단으로 간주해서는 안 됩니다. 특정 DNS 출구를 지정하려면 코어의 DNS 동작, 관련 라우팅, 아웃바운드의 도메인 해석 정책을 함께 확인하세요.

  1. 먼저 앱이 전달한 정보를 확인하세요: HTTP 프록시의 CONNECT 요청에는 보통 도메인이 포함됩니다. SOCKS 요청에는 도메인이 포함될 수도 있고 앱에서 이미 해석한 IP가 전달될 수도 있습니다.
  2. 다음으로 라우팅에서 DNS 조회를 수행하는지 확인하세요: routing.domainStrategy는 라우팅 과정에서 도메인을 IP로 변환해야 하는 시점을 결정합니다.
  3. 마지막으로 아웃바운드 연결 방식을 확인하세요: freedom의 DNS 조회 전략은 아웃바운드 설정에 속합니다. 라우팅 계층에 있는 같은 이름의 개념과는 위치가 다르므로 필드를 잘못된 계층에 복사하지 마세요.

예를 들어 curl에서 socks5://127.0.0.1:10808을 사용하면 보통 curl이 로컬에서 대상 도메인을 조회합니다. socks5h://127.0.0.1:10808을 사용하면 도메인을 SOCKS 프록시 쪽으로 전달합니다. 도메인 라우팅을 테스트할 때는 후자를 우선 사용하세요. 그렇지 않으면 코어에 IP만 전달되어 도메인 규칙이 적용되지 않을 수 있습니다.

전체 설정 예시: 직접 연결과 차단으로 최상위 필드 다섯 가지 확인하기

다음은 기존 JSON 형식으로 작성한 전체 설정입니다. 로컬 HTTP·SOCKS 인바운드와 직접 연결·차단 아웃바운드만 포함하며 원격 프록시 노드는 포함하지 않으므로, 로컬에서 직접 접속할 수 없는 사이트에 자동으로 접속할 수 있게 되지는 않습니다. 설정 구조와 포트 리스닝, 규칙 참조를 먼저 확인한 다음 실제 노드 정보를 추가하는 데 사용할 수 있습니다.

{
  "log": {
    "loglevel": "info"
  },
  "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": "block",
      "protocol": "blackhole",
      "settings": {}
    }
  ],
  "routing": {
    "domainStrategy": "AsIs",
    "rules": [
      {
        "type": "field",
        "domain": ["full:blocked.example"],
        "outboundTag": "block"
      },
      {
        "type": "field",
        "ip": [
          "127.0.0.0/8",
          "10.0.0.0/8",
          "172.16.0.0/12",
          "192.168.0.0/16"
        ],
        "outboundTag": "direct"
      }
    ]
  },
  "dns": {
    "servers": ["localhost"]
  }
}

JSON 객체의 최상위 필드 순서는 실행 순서에 영향을 주지 않습니다. 이 글에서는 읽기 편한 순서로 배치했습니다. 실제 동작에 영향을 주는 것은 규칙 배열 순서, 기본 아웃바운드의 위치, 필드 간 참조 관계입니다. 파일은 UTF-8로 저장하고 키와 문자열에는 영문 큰따옴표를 사용하세요. 마지막 항목 뒤에는 쉼표를 붙이지 말고 설명용 주석도 파일에 복사하지 마세요.

검증 순서: 문법, 시작, 인바운드, 라우팅을 따로 확인하기

전체 예시를 현재 디렉터리에 config.json으로 저장하세요. 다음은 Linux 터미널을 기준으로 JSON 문법을 확인한 뒤 실제 코어로 설정을 검사하는 방법입니다. 문법 검사를 통과해도 JSON을 읽을 수 있다는 뜻일 뿐, 프로토콜 필드와 파일 의존성 또는 네트워크 연결까지 올바르다는 보장은 없습니다.

python3 -m json.tool config.json > /dev/null

# Xray를 사용하는 경우 실행
xray run -test -config config.json

# 해당 명령 형식을 지원하는 V2Ray v5를 사용하는 경우 실행
v2ray test -c config.json

두 코어 검사 명령 중 실제 사용하는 코어에 해당하는 하나만 실행하세요. 테스트를 위해 두 서비스를 동시에 실행할 필요는 없습니다. 구버전 실행 파일은 하위 명령이 다를 수 있으므로 먼저 xray help 또는 v2ray help로 확인하세요. 검사를 통과하면 해당 버전에서 지원하는 실행 명령으로 같은 파일을 불러옵니다. 예를 들어 Xray는 xray run -config config.json을 사용합니다.

# 다른 터미널에서 TCP 리스닝 확인
ss -lntp 'sport = :10808 or sport = :10809'

# HTTP 인바운드를 통해 직접 연결 아웃바운드 테스트
curl --noproxy "" --proxy http://127.0.0.1:10809 --max-time 15 -I https://example.org/

# 도메인을 SOCKS 인바운드로 전달해 차단 규칙 테스트
curl --noproxy "" --proxy socks5h://127.0.0.1:10808 --max-time 5 -I https://blocked.example/

첫 번째 curl 요청의 성공 여부는 로컬 네트워크, 시스템 DNS 조회, 대상 사이트에 따라 달라집니다. 프록시 노드 속도 테스트는 아닙니다. 두 번째 요청은 도메인이 block과 일치하므로 대상의 HTTP 응답을 받지 못하는 것이 예상 결과입니다. 구체적인 오류 메시지는 코어와 연결 단계에 따라 다를 수 있으므로 로그를 함께 확인하세요. 한 번의 시간 초과만으로 라우팅이 적용됐다고 판단해서는 안 됩니다.

요약: 매번 한 단계씩 확인하세요

먼저 리스닝 포트가 열렸는지 확인하고, 명시적으로 프록시를 지정해 요청이 코어에 전달되는지 테스트한 다음 로그를 보고 출구를 확인하세요. 이렇게 하면 '앱이 프록시를 사용하지 않음'과 '프록시에는 들어왔지만 규칙이 잘못됨'을 별개의 문제로 나눌 수 있습니다. 포트, DNS, 노드를 한꺼번에 바꿔 새로운 변수를 만들지 마세요.

  1. 연결이 거부됨: 프로세스가 시작됐는지, 리스닝 주소와 포트가 일치하는지 확인하세요. 이 단계에서는 원격 노드 정보가 우선 점검 대상이 아닙니다.
  2. 인바운드 요청은 들어오지만 출구가 잘못됨: 규칙 순서, 요청에 포함된 정보가 도메인인지 IP인지, 기본 아웃바운드의 위치를 확인하세요.
  3. 규칙은 맞지만 연결에 실패함: DNS 조회 결과, 대상에 대한 연결 가능 여부, 원격 인증 정보와 전송 보안 매개변수를 확인하세요.
  4. 재시작 후 수정 내용이 사라짐: 직접 관리하는 설정을 수정했는지, 클라이언트가 생성한 임시 실행 파일을 수정했는지 확인하세요. 클라이언트를 사용한다면 사용 가이드에서 해당 설정 메뉴를 찾아 변경할 수 있습니다.

이 구조를 익히면 v2rayN이 생성한 설정도 훨씬 쉽게 읽을 수 있습니다. 인바운드 포트, 기본 아웃바운드, 규칙, DNS가 실제로 적용되는 위치 순으로 확인하세요. 플랫폼 설치와 코어 준비 과정은 전체 문서에서 확인할 수 있습니다. 모든 고급 필드를 한 파일에 한꺼번에 넣을 필요는 없습니다.

v2rayN 다운로드