JSON 가이드 · 항목별 찾아보기

V2Ray JSON 설정 가이드

로컬에서 요청을 받아 원격으로 내보내기까지, 설정을 통해 트래픽이 전달되고 처리되는 과정을 알아봅니다.

클라이언트를 처음 사용한다면 먼저 빠른 시작 가이드를 따라 구독을 가져오고 연결을 확인하세요. 이 페이지에서는 설정 항목을 다시 찾아보고, 생성된 설정을 이해하며, 특정 설정이 적용되지 않는 이유를 점검합니다. 데스크톱은 v2rayN, 안드로이드는 v2rayNG를 중심으로 설명합니다.

JSON 구조 Xray / V2Fly 라우팅 및 DNS 로그 확인

1. JSON 구조와 설정 범위

클라이언트와 코어부터 구분하기

v2rayN, v2rayNG, v2flyNG는 구독, 노드, 시스템 프록시와 실행 과정을 관리하는 그래픽 클라이언트입니다. 실제로 설정을 읽고 연결을 처리하는 것은 코어입니다. Project V는 관련 기술 생태계의 기반을 제공하고, V2Fly는 V2Ray의 커뮤니티 개발을 이어가며, Xray는 별도로 발전하는 코어 계열입니다. 여러 개념을 공유하지만 지원하는 설정 항목은 완전히 같지 않습니다. 사용할 JSON이 어떤 코어를 대상으로 하는지 먼저 확인한 다음, 현재 클라이언트가 실제로 어떤 프로그램을 실행하는지 살펴보세요.

이 페이지는 기존 JSON 설정 구조를 중심으로 설명하며, 원격 프로토콜 예시는 주로 Xray에서 흔히 쓰는 형식을 사용합니다. v2rayNG는 보통 Xray를 사용하고, v2flyNG는 V2Fly 코어를 사용하는 안드로이드 대안입니다. v2rayN은 여러 코어를 관리할 수 있지만, 기능은 선택한 코어와 클라이언트 설정에 따라 달라집니다. 메뉴에 모두 '사용자 지정 설정'이 있다고 해서 한 계열의 새 필드를 다른 계열에 그대로 옮겨도 되는 것은 아닙니다. 알 수 없는 필드나 지원되지 않는 프로토콜을 발견하면 구독을 계속 바꾸기보다 먼저 코어를 확인하세요.

최상위 객체가 요청을 연결하는 방식

설정 파일의 최상위는 객체입니다. inbounds는 요청이 들어오는 곳을, outbounds는 요청이 나가는 곳을 설명하고, routing은 둘 사이의 선택을 결정합니다. dns는 내장 DNS 해석기 설정을 제공하고, policy는 일부 연결 및 통계 정책을 관리하며, log는 실행 상태를 확인하는 데 도움을 줍니다. 이 항목들은 작성 순서대로 실행되는 스크립트가 아닙니다. 파일 앞쪽에 놓인 필드가 뒤쪽 모듈보다 먼저 트래픽을 처리하는 것은 아닙니다.

아래는 의도적으로 간소화한 로컬 테스트 설정입니다. 로컬 SOCKS 포트만 수신하고 요청을 받은 뒤 목적지에 직접 연결합니다. JSON 구문, 포트, 앱의 프록시 설정을 확인하는 용도이며 원격 프록시 연결은 설정하지 않습니다. 포트를 다른 프로그램이 사용 중이라면 설정과 테스트 앱에서 모두 포트를 바꿔야 합니다. 처음 구성을 단순하게 유지하면 이후 원격 아웃바운드, 라우팅, DNS를 추가할 때 문제를 일으킨 단계를 찾기 쉽습니다.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "local-socks",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {}
    }
  ]
}

구문이 맞아도 설정 의미까지 맞는 것은 아닙니다

JSON의 객체 키와 문자열은 영문 큰따옴표로 감싸고, 배열은 대괄호를 사용합니다. 불리언은 따옴표로 감싼 문자가 아니라 true 또는 false로 씁니다. 객체의 마지막 항목 뒤에는 쉼표를 붙이지 않습니다. 일부 로더가 주석을 허용하더라도 클라이언트 간 호환성을 확인하기 쉽도록 엄격한 JSON으로 저장하는 편이 좋습니다. 한 객체에 같은 키를 중복해서 쓰지 마세요. 파서에 따라 앞의 값이 덮어써지거나 설정이 거부될 수 있어 실제 동작을 파악하기 어렵습니다.

구문 검사를 통과한 뒤에는 각 필드가 올바른 계층에 있는지 확인해야 합니다. 예를 들어 port는 인바운드 객체에 속하고, 원격 서버의 포트는 해당 프로토콜의 서버 설정에 들어갑니다. streamSettings는 보통 아웃바운드의 settings와 같은 계층에 두며 서버 주소 객체 안에 넣지 않습니다. 구조가 잘못되어도 파일을 읽는 데 실패하지 않고 특정 설정만 무시될 수 있습니다. 예시를 읽을 때는 바깥쪽 괄호 구조를 먼저 파악한 뒤 필드를 복사하세요. 개별 키워드만 대조하는 것보다 정확합니다.

실행 가능한 기준 설정을 복사해 두고, 변경할 때마다 '인트라넷 직접 연결 규칙 추가'처럼 목적을 기록하는 것이 좋습니다. 노드 인증 정보, 구독 주소, 로그에 기록된 접속 대상에는 개인 정보가 포함될 수 있으므로 설정을 공유하기 전에 바꾸세요. 원격 주소가 포함된 예시는 구조 설명을 위한 도메인과 식별자만 사용하며 실제 연결 가능한 서비스가 아닙니다. 위의 로컬 직접 연결 설정만 포트가 비어 있으면 테스트에 바로 사용할 수 있습니다.

2. inbounds: 로컬 요청 진입점

수신 주소, 포트, 프로토콜

인바운드는 세 가지 질문에 답합니다. 어떤 주소에서 연결을 기다릴지, 어떤 포트를 사용할지, 어떤 프로토콜을 받을지입니다. listen을 127.0.0.1로 설정하면 이 IPv4 주소는 로컬 기기에서만 접속할 수 있습니다. 0.0.0.0으로 설정하면 모든 IPv4 인터페이스에서 수신하므로 같은 네트워크의 기기도 접속할 수 있습니다. 후자는 단순히 '호환성이 더 좋은' 범용 옵션이 아닙니다. 방화벽, 네트워크 환경, 접근 제어를 함께 고려해야 합니다. 로컬 브라우저에서만 사용할 때는 루프백 주소를 유지하는 편이 관리하기 쉽습니다.

port는 로컬 서비스 포트이며 원격 노드의 포트가 아닙니다. 두 숫자를 같게 설정해도 특별한 효과는 없습니다. SOCKS와 HTTP는 서로 다른 인바운드 프로토콜이므로 앱에서도 올바른 유형을 선택해야 합니다. 주소와 포트만 맞다고 충분한 것은 아닙니다. 시스템 프록시, 브라우저 확장 프로그램, 터미널 환경 변수가 서로 다른 진입점을 가리킬 수 있습니다. 따라서 특정 브라우저에서 페이지가 열린다고 해서 모든 앱이 같은 인바운드를 사용한다고 볼 수는 없습니다.

HTTP 진입점 추가하기

아래는 두 인바운드를 함께 사용하는 예시입니다. 첫 번째 장의 설정에서 inbounds 배열을 이 내용으로 바꿀 수 있습니다. SOCKS는 해당 프로토콜을 지원하는 앱에, HTTP는 일반 HTTP 프록시와 CONNECT를 지원하는 클라이언트에 사용합니다. 두 포트는 각각 비어 있어야 합니다. HTTP 인바운드는 범용 UDP 진입점이 아닙니다. SOCKS에서 UDP를 허용해도 모든 앱이 SOCKS UDP 요청을 보내는 것은 아닙니다. 해당 기능을 갖춘 앱에서 사용할 수 있도록 하는 설정일 뿐입니다.

"inbounds": [
  {
    "tag": "local-socks",
    "listen": "127.0.0.1",
    "port": 10808,
    "protocol": "socks",
    "settings": {
      "auth": "noauth",
      "udp": true
    }
  },
  {
    "tag": "local-http",
    "listen": "127.0.0.1",
    "port": 10809,
    "protocol": "http",
    "settings": {}
  }
]

tag는 설정 내부에서 사용하는 이름이며, 이후 라우팅에서 inboundTag로 정확하게 참조할 수 있습니다. 용도를 알아볼 수 있는 짧은 이름을 사용하고 중복되지 않도록 하세요. 태그가 있다고 시스템 프록시가 자동으로 생성되지는 않으며, 이름을 'global'로 정한다고 모든 트래픽을 처리하는 것도 아닙니다. 진입점마다 다른 출구를 사용하려면 각각 이름을 지정한 뒤 라우팅 규칙에서 연결 관계를 명시하세요. 클라이언트 화면에 표시되는 이름을 추측해 설정하지 않는 편이 좋습니다.

스니핑은 목적지 정보를 보완합니다

일부 앱은 커널에 목적지 IP만 전달하므로 도메인 규칙을 적용할 근거가 부족할 수 있습니다. 스니핑은 지원되는 초기 프로토콜 데이터에서 HTTP 호스트명이나 TLS 서버 이름 정보를 찾아낼 수 있습니다. 일반적으로 인바운드 객체의 sniffing에 설정하며, enabled를 켠 뒤 destOverride에서 인식할 유형을 지정합니다. 스니핑은 복호화 도구가 아니며 모든 연결에서 도메인을 알아낼 수 있는 것도 아닙니다. 핸드셰이크 정보가 암호화되어 있거나 대상 프로토콜이 아니면 정보를 보완하지 못할 수 있습니다.

"sniffing": {
  "enabled": true,
  "destOverride": ["http", "tls"],
  "routeOnly": true
}

이 설정은 Xray에서 흔히 사용하는 인바운드 추가 옵션입니다. routeOnly는 스니핑한 정보를 라우팅 판단에만 사용하고 실제 연결 목적지를 직접 바꾸지는 않는다는 의미입니다. 구체적인 지원 여부는 코어를 확인해야 합니다. 문제를 점검할 때는 '도메인을 인식했는지', '규칙이 일치했는지', '목적지에 연결됐는지'를 구분하세요. 스니핑 결과가 있다는 사실은 첫 단계만 확인해 줄 뿐, DNS, 원격 전송, 목적지 서비스가 모두 정상이라는 뜻은 아닙니다.

안드로이드 VPN 연결은 일반 프록시와 다릅니다

v2rayNG에서는 시스템 VPN 권한을 받아 클라이언트가 트래픽을 함께 처리합니다. SOCKS 인바운드 하나만 복사한다고 기기 전체의 트래픽을 같은 방식으로 처리할 수는 없습니다. 앱별 프록시, 우회 앱, 백그라운드 실행 제한도 JSON 설정 외부에서 관리합니다. 사용자 지정 설정을 가져온 뒤에는 클라이언트가 해당 설정에 어떻게 연결하는지, 어떤 로컬 포트를 클라이언트가 사용하는지 확인하세요. 클라이언트가 자동으로 생성하는 수신 포트와 중복되지 않도록 주의해야 합니다.

데스크톱에서도 시스템 프록시와 TUN을 구분해야 합니다. 시스템 프록시는 관련 설정을 따르는 프로그램에만 영향을 줍니다. TUN은 가상 인터페이스, 권한, 라우팅 처리를 함께 사용하므로 인바운드 필드 하나만으로 모두 설명할 수 없습니다. 일부 앱에서만 연결되지 않는다면 먼저 앱의 요청이 코어에 들어오는지, 그다음 아웃바운드를 확인하세요. 브라우저와 터미널의 차이는 시스템 프록시 분리 점검 사례를 참고하고, 처음부터 원격 프로토콜 설정을 바꾸지는 마세요.

3. outbounds: 연결 출구와 프로토콜 설정

기본 출구와 태그 참조

아웃바운드는 코어가 목적지에 연결하는 방식을 결정합니다. 하나의 설정에 원격 프록시, 직접 연결, 차단 출구를 함께 둘 수 있지만, 여러 출구가 있다고 자동으로 역할이 분담되지는 않습니다. 일치하는 라우팅 규칙이 없으면 기존 설정에서는 보통 아웃바운드 목록의 첫 항목을 기본 출구로 사용합니다. 따라서 배열 순서를 바꾸면 규칙에 걸리지 않은 요청의 동작이 달라질 수 있습니다. 태그를 무작위로 배열해 결과를 살피기보다 먼저 기본 정책을 정하고 예외를 추가하세요.

라우팅은 outboundTag를 통해 아웃바운드의 tag를 참조하므로 두 값이 정확히 일치해야 합니다. 인터페이스에 표시되는 노드 이름이 런타임 설정의 태그와 같다는 보장은 없습니다. 규칙을 복사한 뒤 이전 이름을 그대로 두면 코어가 대상 출구를 찾지 못할 수 있습니다. 기본 출구를 직접 연결로 설정해 두고 일치하지 않는 트래픽이 모두 프록시를 거칠 거라고 생각하는 경우도 흔한 실수입니다. 설정을 확인할 때는 아웃바운드 배열의 첫 항목부터 살펴보고 각 라우팅 참조를 하나씩 추적하세요.

직접 연결과 차단의 기본 설정

freedom은 요청을 목적지에 직접 연결하고, blackhole은 트래픽을 종료합니다. 아래의 두 객체는 각각 출구로 사용할 수 있으며 기존 아웃바운드 배열에 넣으면 됩니다. 직접 연결은 기기 자체의 네트워크를 사용하므로 목적지에 반드시 접속할 수 있는 것은 아닙니다. 차단 출구는 접속을 대신해 주는 예비 노드가 아닙니다. 문제를 점검할 때 테스트 도메인 하나를 잠시 직접 연결로 지정해 프록시 경로와 로컬 경로를 비교할 수 있습니다. 변경 사항을 기록하고 테스트가 끝나면 되돌리세요.

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

VLESS 예시에서 필드가 속한 위치

아래는 Xray VLESS 아웃바운드 구조 예시입니다. 도메인과 사용자 식별자는 설명용이므로 연결하려면 서버에서 실제로 제공한 값으로 바꿔야 합니다. address는 원격 연결에 사용할 주소, port는 서버 수신 포트, users는 해당 프로토콜의 사용자 설정 목록입니다. id는 사용자 식별자이며 encryption의 형식은 프로토콜에 따라 정해집니다. 여기서 none은 전송 계층에도 보안이 없다는 뜻이 아닙니다.

{
  "tag": "proxy",
  "protocol": "vless",
  "settings": {
    "vnext": [
      {
        "address": "edge.example.org",
        "port": 443,
        "users": [
          {
            "id": "00000000-0000-4000-8000-000000000001",
            "encryption": "none",
            "level": 0
          }
        ]
      }
    ]
  },
  "streamSettings": {
    "network": "ws",
    "security": "tls",
    "tlsSettings": {
      "serverName": "edge.example.org"
    },
    "wsSettings": {
      "path": "/tunnel"
    }
  }
}

이 설정에서 프로토콜 설정과 전송 설정은 서로 다른 계층입니다. VLESS 사용자 식별자가 맞더라도 WebSocket 경로가 틀리면 연결에 실패할 수 있습니다. TLS 핸드셰이크에 성공했다고 사용자 인증까지 통과한 것은 아닙니다. 서버에서 요구하는 흐름 제어 옵션, 전송 유형, 기타 매개변수를 하나씩 일치시켜야 합니다. 프로토콜 이름이 같다고 노드 설정까지 같다고 볼 수는 없습니다. 예시에는 선택 항목이 모두 포함되어 있지 않으므로 보이지 않는 값을 추측해 추가하지 마세요.

프로토콜별 설정 구조를 섞지 않기

VMess에서도 vnext 구조를 흔히 사용하지만 사용자 설정의 의미는 VLESS와 다릅니다. Trojan은 보통 servers 배열과 비밀번호 필드를 사용합니다. Trojan 비밀번호를 VLESS의 id에 넣거나 한 프로토콜의 사용자 객체를 다른 프로토콜에 그대로 적용하지 마세요. 노드 공유 링크를 가져온 뒤에는 긴 설정 파일을 직접 조립하기보다 클라이언트 화면에서 프로토콜과 주요 매개변수를 확인하는 편이 빠뜨릴 내용이 적습니다.

아웃바운드에는 도메인 해석, 인터페이스 바인딩, 체인 프록시 등의 고급 옵션도 포함될 수 있습니다. 코어별 차이가 큰 기능이므로 필요성이 명확할 때만 추가하세요. '예비 출구'를 하나 더 설정한다고 자동으로 장애 전환이 되는 것은 아닙니다. 아웃바운드를 하나 더 작성해도 코어가 자동으로 속도를 측정하고 더 빠른 출구를 선택하지 않습니다. 자동 선택에는 별도의 선택 메커니즘, 상태 확인 로직, 클라이언트 기능이 필요할 수 있으므로 사용 중인 코어에 맞춰 설정하세요.

가져온 설정에서 특정 아웃바운드만 실패한다면 나머지 정상 설정은 그대로 두고 해당 항목의 주소, 인증 정보, 전송 설정만 바꿔 비교하세요. 출구 순서와 라우팅을 동시에 수정하지 마세요. 연결이 복구되더라도 노드가 정상화된 것인지 트래픽이 다른 출구로 이동한 것인지 알기 어렵습니다.

4. 전송 및 보안 계층: 설정을 나누어 확인하기

프로토콜, 전송 방식, 암호화는 서로 다릅니다

노드 설정은 세 계층으로 나누어 볼 수 있습니다. 애플리케이션 프록시 프로토콜은 인증과 전달 방식을 정의하고, 전송 방식은 연결을 전달하는 방법을, 보안 계층은 관련 핸드셰이크와 보호 기능을 담당합니다. 예를 들어 VLESS, WebSocket, TLS는 각각 다른 계층에 속합니다. 프로토콜 이름이 맞는 것은 시작일 뿐입니다. 서버가 WebSocket을 기다리는데 클라이언트가 일반 스트림 연결을 사용하면 포트가 같아도 연결되지 않습니다.

streamSettings.network에서 전송 방식을 선택하고 streamSettings.security에서 해당 계층의 보안 방식을 선택합니다. 구체적인 옵션은 각 객체에 설정합니다. 전송 이름과 이전·신규 필드 지원 여부는 코어에 따라 달라질 수 있으므로 최신 코어용 예시를 모든 버전에서 쓸 수 있는 범용 설정으로 간주하면 안 됩니다. 가져온 원본 설정을 보존한 뒤 명확한 오류 하나를 기준으로 수정하세요. 특히 비슷해 보이는 네트워크 이름을 한꺼번에 바꾸지 마세요.

WebSocket과 TLS 비교

아래는 전송 설정 일부입니다. 단독으로 실행하는 완성 설정이 아니라 해당 아웃바운드 객체에 넣어야 합니다. 경로는 서버 또는 앞단 리버스 프록시 설정과 일치해야 하며 앞의 슬래시도 포함해야 합니다. 실제 경로에 쿼리 매개변수가 있다면 그 의미도 유지하세요. serverName은 TLS 서버 이름 처리에 사용하고 WebSocket의 Host는 HTTP 요청 계층에 속합니다. 두 값이 같은 경우가 많지만 같은 필드로 취급해서는 안 됩니다.

"streamSettings": {
  "network": "ws",
  "security": "tls",
  "tlsSettings": {
    "serverName": "edge.example.org",
    "allowInsecure": false
  },
  "wsSettings": {
    "path": "/tunnel",
    "headers": {
      "Host": "edge.example.org"
    }
  }
}

실제 연결 주소는 도메인일 수도, 서비스 제공자가 지정한 주소일 수도 있습니다. 인증서 이름, TLS 서버 이름, HTTP 호스트명은 각각 다른 단계에서 사용됩니다. 주소에 연결할 수 있지만 인증서 이름이 일치하지 않는다면 서비스에서 안내한 도메인과 기기의 시간을 확인하세요. allowInsecure를 곧바로 true로 바꾸지 마세요. 검증을 건너뛰면 인증서나 대상 주소 문제를 가릴 수 있으므로 일상적인 해결 방법으로 사용해서는 안 됩니다.

매개변수주요 역할자주 확인할 항목
address원격 연결 대상도메인 해석 및 주소 연결 가능 여부
serverNameTLS 서버 이름 처리인증서 이름 및 서버 요구 사항
headers.HostWebSocket HTTP 호스트명앞단 서비스의 가상 호스트 일치 여부
pathWebSocket 요청 경로대소문자, 슬래시, 전체 경로

REALITY와 다른 전송 방식의 차이

REALITY 클라이언트 매개변수는 일반 TLS 설정과 다르므로 security 이름만 바꾼다고 전환이 끝나는 것이 아닙니다. 서버 이름, 키 관련 매개변수, 짧은 식별자 등을 서버 설정과 일치시켜야 하며, 필드 지원 여부는 코어에 따라 다릅니다. 공유 링크를 가져올 때도 클라이언트가 모든 매개변수를 인식했는지 확인하세요. 인터페이스에 해당 설정이 없다면 임의로 이름이 비슷한 객체에 넣지 말고 먼저 지원 여부를 확인하세요.

마찬가지로 gRPC 서비스 이름은 WebSocket 경로와 다르며, HTTP 전송의 요청 헤더를 다른 유형에 임의로 재사용할 수 없습니다. 전송 방식을 바꾸려면 일반적으로 서버도 함께 설정해야 합니다. 클라이언트에서만 전환해도 서버가 자동으로 호환되지는 않습니다. 노드 두 개를 비교할 때는 먼저 동일한 서비스 설정을 사용하는지 확인한 뒤 동작 차이를 살펴보세요. 그렇지 않으면 같은 전송 방식의 성능이 아니라 서로 다른 연결 경로를 비교하게 됩니다.

실패 단계에 따라 로그 읽기

연결 시간 초과가 발생하면 먼저 주소, 포트, 경로에 접근할 수 있는지 확인하세요. 인증서 오류라면 이름, 인증서 체인, 시스템 시간을 중점적으로 살펴보세요. HTTP 상태 코드는 반환됐지만 업그레이드에 실패했다면 경로, 호스트명, 앞단 서비스 설정을 확인하는 것이 일반적입니다. 인증 실패는 프록시 프로토콜 계층에서 사용자 식별자나 비밀번호를 확인해야 합니다. 로그 오류가 속한 계층부터 살펴보면 모든 실패를 DNS 문제로 돌리는 일을 피할 수 있습니다.

문제를 점검할 때는 노드 하나, 앱 하나, 고정 테스트 대상 하나만 사용하고 요청을 변경할 수 있는 프록시 확장 프로그램은 끄세요. 비교 가능한 환경을 유지하는 것이 중요합니다. 전송, 인증서 설정, 라우팅 모드를 동시에 바꾸지 마세요. 한 번에 변수 하나만 바꾸고 실패 단계가 앞뒤로 이동하는지 기록하세요. '연결을 설정할 수 없음'에서 '인증 실패'로 바뀌었다면 아직 연결되지는 않았더라도 앞선 네트워크와 핸드셰이크 단계는 진전된 것입니다.

5. routing: 규칙 순서와 대상 매칭

위에서 아래로 확인하고, 먼저 일치한 규칙으로 출구 선택

라우팅은 일치하는 결과를 모두 모아 점수를 매기는 방식이 아닙니다. 일반적인 필드 규칙은 배열 순서대로 확인하며, 처음으로 일치하는 규칙의 출구를 선택합니다. 따라서 구체적인 예외 규칙을 넓은 규칙보다 앞에 배치해야 합니다. '모든 트래픽을 프록시로' 보내는 규칙이 너무 앞에 있으면 뒤에 있는 인트라넷 직접 연결 규칙이 적용되지 않습니다. 규칙이 있는지만 검색하지 말고 그 앞에 요청을 먼저 받아 갈 규칙이 있는지도 살펴보세요.

규칙에서 서로 다른 조건 필드는 보통 모두 충족해야 하지만, 같은 필드의 배열에 있는 값은 여러 후보로 취급됩니다. 예를 들어 한 규칙에 inboundTag와 domain을 함께 쓰면 지정된 인바운드에서 들어왔고 대상 도메인도 일치해야 합니다. 둘 중 하나만 만족하면 된다는 뜻이 아닙니다. '도메인이 특정 집합에 속하거나 대상 IP가 특정 대역에 속함'을 표현하려면 규칙을 둘로 나누세요. 로그에서 어떤 조건이 적용됐는지 확인하기도 쉽습니다.

읽기 쉬운 라우팅 규칙 예시

아래 예시는 설명용 특정 도메인을 먼저 차단하고, 사설 주소는 직접 연결한 다음, 지정된 도메인과 하위 도메인은 프록시로 보냅니다. 기존 아웃바운드에 block, direct, proxy 태그가 있어야 합니다. geoip:private를 사용하려면 GeoIP 데이터 파일이 필요합니다. 데이터가 준비되지 않았다면 먼저 명시적인 IP 대역으로 테스트하세요. 집합 이름만으로 모든 주소가 포함된다고 가정하면 안 됩니다.

"routing": {
  "domainStrategy": "AsIs",
  "rules": [
    {
      "type": "field",
      "domain": ["full:blocked.example.org"],
      "outboundTag": "block"
    },
    {
      "type": "field",
      "ip": ["geoip:private"],
      "outboundTag": "direct"
    },
    {
      "type": "field",
      "domain": ["domain:example.org"],
      "outboundTag": "proxy"
    }
  ]
}

full:은 도메인 전체가 일치하는 경우, domain:은 도메인과 하위 도메인이 일치하는 경우에 사용합니다. regexp:는 정규식, geosite:는 도메인 집합을 참조합니다. 접두사 없이 입력한 일반 문자열은 더 넓은 범위로 일치할 수 있으므로 특정 도메인을 지정하는 용도로 적합하지 않습니다. 호스트 하나만 대상으로 하려면 명확한 접두사를 사용해 이름에 같은 문자열이 들어간 다른 도메인까지 규칙에 포함되지 않도록 하세요.

도메인 해석 정책이 매칭 과정에 미치는 영향

AsIs는 IP 규칙을 위해 모든 도메인을 미리 해석하지 않고 요청에 포함된 기존 대상 정보를 최대한 사용합니다. 따라서 위 예시처럼 도메인으로 요청한 경우 실제 해석 결과가 사설 IP라고 해서 반드시 사설 IP 규칙에 일치하는 것은 아닙니다. IPIfNonMatch는 도메인 규칙에 일치하지 않으면 해석을 시도한 뒤 관련 규칙을 다시 확인합니다. IPOnDemand는 매칭에 IP 정보가 필요할 때 해석을 시도합니다. 두 방식 모두 추가적인 DNS 해석에 의존합니다.

이 옵션들은 주로 라우팅 판단에 영향을 줍니다. '모든 트래픽을 강제로 로컬에서 해석'하거나 'DNS가 반드시 프록시를 통과'하는 설정과 같은 뜻은 아닙니다. 도메인을 로컬에서 해석할지 원격에 맡길지는 선택한 아웃바운드와 프로토콜 동작에도 영향을 받습니다. 정책을 바꾼 뒤 웹페이지가 처음 열릴 때 느려졌다면 DNS 해석 대기가 추가됐는지 먼저 확인하세요. IP 집합 매칭이 명확히 필요할 때만 해당 정책을 사용하고, 일찍 해석할수록 규칙이 정확해진다고 생각하지 마세요.

데이터 파일과 인터페이스 규칙의 관계

geosite.dat와 geoip.dat는 각각 도메인 및 IP 집합 데이터를 제공합니다. 커널 설정의 일반 주석이 아닙니다. 집합 이름이 현재 데이터 파일에 존재해야 하며, 파일을 불러오지 못하거나 이름을 잘못 쓰면 실행에 실패할 수 있습니다. 업데이트에 따라 분류 내용도 달라질 수 있으므로 문제를 확인할 때 데이터 출처와 변경 내용을 기록하세요. 업데이트 경로와 규칙 순서는 GeoIP 및 GeoSite 사용 안내를 참고하세요.

v2rayN의 라우팅 프리셋과 v2rayNG의 사용자 지정 규칙은 런타임 설정을 생성할 때 인터페이스 옵션과 함께 적용될 수 있습니다. 파일을 수정했는데도 이전 규칙처럼 동작한다면 현재 선택된 설정과 모드를 먼저 확인한 뒤 클라이언트가 파일을 다시 생성했는지 살펴보세요. 검증할 때는 구체적인 도메인 하나와 확인된 사설 주소 하나를 각각 테스트하고 출구 태그를 확인하세요. 웹페이지 하나가 열렸다는 사실만으로 모든 분기 규칙이 예상대로 동작한다고 볼 수는 없습니다.

6. dns: 해석 출처와 연결 경로

내장 DNS와 시스템 DNS는 다릅니다

설정의 dns는 코어에 내장된 해석기의 동작을 정의하지만 기기에 있는 모든 앱의 DNS 설정을 자동으로 바꾸지는 않습니다. 브라우저가 자체 암호화 DNS를 사용할 수 있고 앱이 시스템 해석기를 호출할 수도 있으며, 안드로이드 VPN에서 DNS 트래픽 처리를 클라이언트가 정할 수도 있습니다. 문제를 확인하기 전에 어떤 프로그램의 DNS 요청이 실패했는지, 요청이 어디서 나왔는지, 어떤 해석기가 응답하는지 파악하세요. 코어의 DNS만 바꿔 놓고 브라우저 자체 해석 경로를 계속 테스트하면 예상한 변화를 확인하기 어렵습니다.

라우팅 모듈, 직접 연결 아웃바운드, 기타 기능이 내장 해석기를 사용하는지는 구체적인 옵션에 따라 다릅니다. 앱이 이미 IP를 전달했다면 코어는 원래 도메인을 다시 해석할 필요가 없을 수 있습니다. 앱이 도메인을 프록시에 전달하면 규칙이나 원격 처리에 사용할 도메인 정보가 유지될 수 있습니다. 따라서 로그에 DNS 조회가 없다고 해서 DNS 모듈이 고장 난 것은 아닙니다. 요청에 이미 주소가 포함되어 있거나 현재 처리 과정에서 로컬 해석이 필요하지 않을 수도 있습니다.

간단한 DNS 기준 설정부터 만들기

아래 설정은 일반 DNS 서버 두 개를 지정하고 코어에서 해석이 필요할 때 IPv4 결과만 우선 가져옵니다. 서버 목록과 조회 정책을 이해하기 위한 기본 예시일 뿐, 모든 네트워크에서 해당 주소에 접속할 수 있다거나 조회가 자동으로 프록시를 통과한다는 뜻은 아닙니다. 실제 환경에서 접속할 수 있고 사용해도 괜찮은 해석 서비스를 선택한 뒤, 코어의 동작에 맞게 조회 경로를 설정하세요.

"dns": {
  "servers": [
    "1.1.1.1",
    "8.8.8.8"
  ],
  "queryStrategy": "UseIPv4"
}

UseIPv4는 내장 해석기가 어떤 IP 버전의 결과를 조회할지 제한할 뿐, 운영체제의 IPv6를 끄거나 앱에서 직접 시작한 IPv6 연결을 바꾸지 않습니다. 일부 주소 계열 문제를 분리하는 데 쓸 수 있지만 모든 연결 실패에 대한 해결책으로 여겨서는 안 됩니다. 기기와 노드 모두 안정적인 IPv6를 지원한다면 실제 대상에 연결해 본 뒤 정책을 정하세요. 일시적인 문제를 우회하려다 원래 작동하던 연결 경로를 계속 잃지 않도록 주의해야 합니다.

정적 매핑과 도메인별 해석

hosts는 특정 이름을 내장 해석기에서 사용하는 정적 주소로 매핑할 수 있습니다. 아래 예시는 설명용 테스트 도메인을 루프백 주소에 연결하므로 로컬 서비스의 도메인 해석 단계를 확인하는 데 적합합니다. 시스템의 hosts 파일을 수정하는 설정은 아니며, 해당 도메인이 포함된 모든 아웃바운드가 자동으로 바뀌는 것도 아닙니다. 이 해석 절차를 사용하는 동작에만 매핑이 적용됩니다. 테스트가 끝나면 삭제해 원격으로 접속해야 할 요청이 로컬 기기로 돌아오지 않도록 하세요.

"dns": {
  "hosts": {
    "lab.example.org": "127.0.0.1"
  },
  "servers": [
    "1.1.1.1"
  ],
  "queryStrategy": "UseIPv4"
}

더 복잡한 DNS 설정에서는 일부 도메인에 사용할 해석기를 지정하거나 예상 IP 범위와 대체 동작을 설정할 수 있습니다. 그러나 옵션 조합은 코어에 따라 다르므로 서버 객체 하나만 복사한다고 완전한 분기 해석이 구현되는 것은 아닙니다. 특히 지정한 서버에서 허용 가능한 결과를 받지 못했을 때 어떻게 처리할지, 대체를 허용할지, 어떤 서버로 돌아갈지 명확히 해야 합니다. 해석기 설정을 여러 개 쌓기보다 먼저 분기 구조를 그려 두는 편이 관리하기 쉽습니다.

DNS 해석이 자기 자신에 의존하지 않도록 하기

암호화 DNS를 사용할 때 해석 서버의 도메인을 먼저 해석해야 할 수 있습니다. 이 작업이 아직 연결되지 않은 프록시에 의존하면 시작 단계에서 순환 의존성이 생깁니다. 프록시 서버 주소가 도메인이고 그 도메인 해석도 해당 프록시를 거쳐야 하는 설정 역시 연결을 시작하지 못할 수 있습니다. 문제를 점검할 때는 연결 가능한 기본 해석 경로를 남겨 둔 뒤 암호화 해석과 라우팅 조건을 단계적으로 추가하세요. 확인되지 않은 출구 하나에 모든 조회를 처음부터 묶지 마세요.

DNS 요청을 보내는 방식도 서버 주소의 형식에 영향을 받습니다. 일반 UDP 조회, 암호화 조회, 일부 로컬 처리 방식은 라우팅 동작이 서로 다를 수 있습니다. DNS에 태그 하나만 추가했다고 모든 조회가 그 태그를 통해 라우팅되는 것은 아닙니다. 현재 코어에서 해당 서버 형식을 어떻게 처리하는지 확인하고 로그와 실제 네트워크 동작으로 검증하세요. 도메인 해석에 성공했다는 것은 주소를 받았다는 뜻일 뿐, 그 주소에 최종 출구로 연결할 수 있다는 뜻은 아닙니다.

노드에는 연결된 것으로 표시되는데 웹페이지가 열리지 않는다면 먼저 같은 대상의 해석 결과와 연결 오류를 비교하고 시스템 시간, IP 버전, 앱별 프록시 설정을 확인한 뒤 DNS 변경이 필요한지 판단하세요. 전체 점검 순서는 연결됐지만 접속되지 않을 때 확인할 항목을 참고하세요. 한 번에 해석기 하나만 바꾸고 연결을 다시 시도하세요. 앱 캐시 때문에 이전 결과가 계속 사용될 수 있다는 점도 고려해야 합니다.

7. policy: 연결 정책과 통계 옵션

정책 레벨은 시스템 권한 레벨이 아닙니다

policy는 일부 연결 동작을 정책 객체에서 관리합니다. levels의 키는 사용자 레벨 식별자입니다. 예를 들어 문자열 키 "0"은 레벨 0을 뜻하며, 프로토콜 사용자 객체에서 해당 level 필드로 참조할 수 있습니다. 이 레벨은 관리자 권한이나 노드 품질 점수가 아닙니다. 별도의 등급 구분이 필요하지 않다면 여러 레벨을 추가하기보다 기본 정책부터 이해하는 편이 관리하기 쉽습니다.

모든 인바운드와 아웃바운드가 사용자 레벨을 똑같이 사용하는 것은 아닙니다. JSON에 필드를 쓸 수 있다고 해서 현재 프로토콜에 해당 사용자 컨텍스트가 반드시 있는 것은 아닙니다. 정책이 적용되는지 판단하려면 프로토콜 지원, 레벨 참조, 연결 유형을 함께 살펴봐야 합니다. 특히 levels에 객체를 추가하기만 하면 모든 트래픽이 자동으로 해당 레벨을 사용한다고 생각하면 안 됩니다. 이 기능을 지원하는 위치에서 연결 관계를 명시해야 합니다.

시간 초과 매개변수는 단계별로 이해하기

handshake는 해당 연결의 핸드셰이크 대기 시간을 제한하고, connIdle은 유효한 데이터 활동이 없는 유휴 단계를 처리합니다. uplinkOnly와 downlinkOnly는 연결에서 한 방향만 계속 동작하는 상황에 해당합니다. 네 가지 모두 서로 대체하는 '연결 시간 초과' 옵션이 아니며, 값을 줄일수록 민감해지는 것도 아닙니다. 모바일 네트워크 전환, 장시간 연결을 사용하는 알림, 느린 핸드셰이크는 필요한 대기 시간이 서로 다를 수 있으므로 실제 문제에 맞춰 조정하세요.

"policy": {
  "levels": {
    "0": {
      "handshake": 4,
      "connIdle": 300,
      "uplinkOnly": 2,
      "downlinkOnly": 5
    }
  },
  "system": {
    "statsInboundUplink": false,
    "statsInboundDownlink": false,
    "statsOutboundUplink": false,
    "statsOutboundDownlink": false
  }
}

위 예시의 시간 값은 초 단위이며 계층 구조를 보여주기 위한 것입니다. 모든 기기에 권장하는 최적화 설정은 아닙니다. 핸드셰이크가 완료되기 전에 연결이 종료된다면 먼저 네트워크 경로와 서버 응답을 확인한 뒤 대기 시간이 실제로 너무 짧은지 살펴보세요. 오랫동안 사용하지 않은 뒤 연결이 다시 만들어진다면 앱의 재연결 동작, 시스템의 백그라운드 제한, 네트워크 변화를 함께 고려하세요. '연결 끊김'이라는 문구만 보고 모든 시간 초과 값을 늘리지는 마세요.

통계는 모듈과 옵션을 함께 설정해야 합니다

policy.system의 통계 옵션은 각 방향의 데이터 통계를 제어합니다. 코어 쪽에서는 일반적으로 최상위 stats 모듈도 활성화해야 합니다. 클라이언트에서 데이터를 읽으려면 관련 조회 인터페이스나 클라이언트 자체 연동 기능도 필요합니다. 옵션만 켠다고 화면에 통계 패널이 자동으로 생기지는 않습니다. 통계 종류도 구분해야 합니다. 인바운드 통계는 진입점, 아웃바운드 통계는 출구, 사용자 통계는 식별 가능한 사용자와 관련 설정에 따라 달라집니다.

"stats": {},
"policy": {
  "system": {
    "statsInboundUplink": true,
    "statsInboundDownlink": true,
    "statsOutboundUplink": true,
    "statsOutboundDownlink": true
  }
}

이 설정은 최상위 설정 일부입니다. 기존 설정에 합칠 때는 policy 객체에 통합하고 같은 계층에 같은 키를 두 번 쓰지 마세요. 통계는 보통 코어가 처리한 데이터를 보여주며 통신사 과금 트래픽과 같지는 않습니다. 연결 헤더, 재전송, 시스템의 다른 트래픽이 같은 기준에 포함되지 않을 수 있습니다. 클라이언트의 누적량도 재시작 후 다시 시작될 수 있으므로 화면마다 표시되는 숫자를 바로 빼기 전에 통계 범위와 집계 기간을 확인하세요.

성능 문제를 정책 매개변수 추가로 해결하려 하지 않기

속도가 느린 원인은 대개 대상 서비스, 원격 노드 부하, 네트워크 경로, 로컬 트래픽 처리 방식에 따라 달라집니다. 정책의 대기 시간은 연결 수명을 관리할 뿐 사용 가능한 대역폭을 늘려 주지 않습니다. 일부 코어에서는 연결 버퍼를 조정할 수 있지만 버퍼 크기가 처리량과 같지는 않으며 너무 키우면 메모리 부담이 생길 수 있습니다. 재현 가능한 테스트 결과를 먼저 확보하고 특정 매개변수가 실제 문제와 관련 있는지 판단하세요.

안드로이드 기기에서는 배터리 제한과 백그라운드 실행 조건도 확인해야 합니다. 데스크톱에서는 시스템 프록시, TUN, 다른 네트워크 도구가 서로 영향을 주는지 살펴보세요. 이런 요소가 모두 policy에 속하는 것은 아닙니다. 앱이 백그라운드에서 종료된 뒤에만 연결이 끊긴다면 유휴 시간을 크게 늘리기보다 클라이언트가 계속 실행 중인지 먼저 확인하세요. 특정 시간대에만 느리다면 노드별 결과와 직접 연결 결과를 비교하고 느린 속도를 단계별로 진단하는 방법을 참고하세요.

8. 설정 병합, 로그 검증, 되돌리기

조각을 합쳐 유효한 설정 파일 만들기

이 페이지의 예시는 각각 일부 설정 조각이므로 그대로 이어 붙이면 안 됩니다. 최상위 객체는 하나만 유지하고 인바운드 객체는 하나의 inbounds 배열에, 출구 객체는 하나의 outbounds 배열에 넣으세요. 라우팅 규칙은 기존 rules에 추가한 뒤 순서를 다시 확인해야 합니다. 최상위 dns, policy, log는 각각 하나의 객체만 유지해 중복 키로 앞의 설정이 덮어써지지 않도록 하세요.

구문을 확인한 뒤 요청 하나의 경로를 따라가 보세요. 앱이 올바른 인바운드에 연결되는지, 진입점 태그가 무엇인지, 요청에 도메인이 유지되는지 아니면 IP만 있는지, 처음 일치한 규칙이 무엇인지, 선택된 아웃바운드 태그가 존재하는지, 원격 전송 설정이 완전한지 확인합니다. 이 과정을 직접 추적하면 '설정 파일은 실행되지만 접속 방식이 잘못된' 문제를 많이 찾을 수 있습니다. 특히 기본 출구를 확인해 테스트가 예기치 않은 직접 연결로 성공한 것은 아닌지 살펴보세요.

먼저 클라이언트에서 설정이 로드됐는지 확인하기

v2rayN에서는 현재 선택된 설정, 코어 유형, 라우팅 모드를 확인한 다음 로그 창을 살펴보세요. v2rayNG에서는 현재 설정 항목, VPN 권한, 서비스 상태를 확인합니다. 메뉴 이름은 클라이언트 업데이트에 따라 바뀔 수 있으므로 현재 화면을 기준으로 하세요. 사용자 지정 설정은 일반 노드 편집과 관리 방식이 다를 수 있습니다. 수정한 뒤 관련 서비스를 다시 시작하고 로그에 방금 수정한 설정이 실제로 로드됐는지 확인하세요. 다른 설정 항목이 로드된 것은 아닌지 주의해야 합니다.

로그 수준을 잠시 info로 높이면 연결 대상과 출구를 확인하기 쉽습니다. 문제 해결이 끝나면 기록되는 내용을 줄이기 위해 warning으로 되돌릴 수 있습니다. 설정 파일을 파싱하지 못한 경우 파일 안의 로그 설정도 적용되지 않을 수 있으므로 클라이언트에 표시된 시작 로그를 확인하세요. 로그 파일이 생성되지 않았다는 이유만으로 코어가 시작을 시도하지 않았다고 판단하지 마세요.

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

고정된 대상으로 로컬 진입점 확인하기

데스크톱에서 사용할 수 있는 명령줄 요청 도구가 있다면 로컬 프록시를 명시해 시스템 프록시 적용 여부와 분리해서 테스트할 수 있습니다. 아래 두 명령은 2장의 포트 설정에 맞춰 SOCKS와 HTTP 진입점을 각각 테스트합니다. SOCKS 명령은 도메인을 프록시 쪽 처리 과정으로 전달하고, HTTP 명령은 로컬 HTTP 프록시로 같은 설명용 사이트에 접속합니다. 이 명령은 요청이 지정한 진입점을 통해 완료되는지 확인하는 용도이며 속도 측정용이 아닙니다.

curl --proxy socks5h://127.0.0.1:10808 https://example.org/
curl --proxy http://127.0.0.1:10809 https://example.org/

명령에서 로컬 연결이 거부되면 먼저 수신 포트와 서비스 상태를 확인하세요. 코어까지 요청이 들어왔지만 원격 연결이 시간 초과되면 출구와 네트워크를 살펴보세요. 명령은 성공하지만 브라우저에서 실패한다면 브라우저 확장 프로그램, 별도의 프록시 설정, DNS 동작을 확인하세요. 안드로이드에서는 브라우저와 테스트 대상을 고정해 반복 실행하고 v2rayNG 로그와 앱별 설정을 함께 살펴보면 됩니다. 데스크톱 명령을 그대로 따라 하려고 별도 도구를 설치할 필요는 없습니다.

오류 유형별로 점검 범위 좁히기

증상우선 확인할 항목다음 단계
JSON 읽기 실패따옴표, 쉼표, 괄호, 계층 구조가장 최근에 수정한 객체부터 확인
수신 실패포트 사용 중 여부와 주소 권한충돌하는 프로그램을 종료하거나 포트 변경
집합을 찾을 수 없음데이터 파일과 집합 이름로드 경로와 규칙 철자 확인
핸드셰이크 또는 인증 실패전송, 보안 계층, 사용자 설정실패 단계에 따라 항목별 비교
일부 앱에서만 실패프록시 진입점과 트래픽 처리 범위명시적 프록시와 시스템 설정 비교

로그의 마지막 줄은 앞선 오류의 결과일 수 있습니다. 예를 들어 연결 종료는 그보다 앞선 DNS 또는 인증 오류로 발생했을 수 있습니다. 같은 시도의 맥락을 유지하고 마지막 줄만 잘라 보기보다 처음 나타난 명확한 오류부터 읽으세요. 로그를 공유하기 전에 사용자 식별자, 비밀번호, 구독 주소, 접속 대상을 바꾸고 필드 이름, 오류 유형, 필요한 구조만 남기세요. 시작 오류를 더 자세히 분석하려면 v2rayNG 코어 로그 읽는 방법을 참고하세요.

변경할 때마다 되돌릴 지점 남기기

안전한 순서는 기준 설정이 정상적으로 실행되는지 확인하고, 원격 아웃바운드를 추가해 연결 하나를 검증한 다음 라우팅, DNS, 정책을 추가하는 것입니다. 각 단계에서 사본을 저장하고 변경 목적을 기록하세요. 문제가 생기면 마지막으로 작동한 설정으로 되돌린 뒤 기기와 네트워크가 정상인지 확인하고 실패한 변경만 다시 적용하세요. 이렇게 하면 환경 변화와 설정 문제를 구분할 수 있고, 이미 복잡해진 파일에 수정 사항을 계속 덧붙이는 일을 피할 수 있습니다.

일상적으로 관리해야 하는 설정은 가능한 한 클라이언트에서 관리할 수 있는 위치에 두고, 구독을 업데이트한 뒤 어떤 설정이 유지되고 무엇이 다시 생성되는지 확인하세요. 이 가이드는 설정의 내부 관계를 이해하기 위한 것으로 매번 JSON 전체를 직접 작성할 필요는 없습니다. 가져오기와 연결 절차를 다시 살펴보려면 빠른 시작 가이드로 돌아가세요. 운영체제에 맞는 프로그램으로 바꾸려면 클라이언트 페이지에서 해당 항목을 선택하세요.