핵심 내용

먼저 받은 자료가 구독 주소인지, 인코딩된 링크 목록인지, 전체 코어 설정 파일인지 확인한 다음 알맞은 가져오기 메뉴를 선택하세요. ‘구독을 가져왔는데 목록이 비어 있음’, ‘JSON을 인식하지 못함’, ‘변환 후 설정이 사라짐’ 문제를 겪는 사용자를 위해 VMess·VLESS 필드부터 로컬 Base64 변환, 클라이언트 가져오기, Xray 설정 매핑까지 살펴봅니다. 공유 링크에 포함할 수 없는 정보도 함께 설명합니다.

1. 구독 주소, Base64, JSON의 차이

‘V2Ray 구독’은 하나의 고정된 파일 형식이 아닙니다. 흔히 받는 HTTPS 주소는 실제 내용을 가져오는 경로일 뿐입니다. 클라이언트가 주소에 접속해야 응답을 받을 수 있습니다. 응답은 한 줄에 하나씩 공유 링크가 들어 있는 텍스트일 수도 있고, 링크 목록 전체를 Base64로 인코딩한 문자열일 수도 있습니다. 일부 서비스는 요청 헤더에 따라 서로 다른 내용을 반환하므로 주소의 확장자만으로 형식을 판단할 수 없습니다.

항목구별하는 방법적절한 처리 방법
구독 주소보통 https://로 시작하며 접근 토큰이 포함될 수 있음구독 그룹에 등록한 뒤 필요할 때 업데이트
일반 텍스트 링크 목록각 줄이 vmess://, vless:// 같은 프로토콜 접두사로 시작함클립보드에서 여러 링크 가져오기
Base64로 인코딩된 구독 내용대개 긴 인코딩 문자열이며 디코딩하면 공유 링크가 나타남구독 파서에서 처리하거나 먼저 로컬에서 디코딩
VMess 링크에 포함된 JSON일반적인 필드는 v, add, id, netVMess 공유 링크 한 개를 만들거나 파싱할 때 사용
Xray 기본 설정 파일최상위 필드로 inbounds, outbounds, routing 등이 자주 사용됨해당 코어에서 직접 사용하거나 사용자 지정 설정 메뉴로 불러오기

둘 다 JSON이라고 해서 서로 바꿔 쓸 수 있는 것은 아닙니다. VMess 공유 객체는 서버 한 대와 전송 매개변수를 설명하지만, Xray 설정은 코어가 로컬 포트를 열고 아웃바운드를 만들며 라우팅을 처리하는 방식을 정의합니다. 전자를 config.json으로 저장해도 후자로 바뀌지 않습니다. 후자 전체를 Base64로 인코딩해도 클라이언트가 지원하는 구독 형식이 되는 것은 아닙니다.

2. VMess·VLESS 공유 링크의 필드 구조

일반적인 VMess 공유 형식은 vmess:// 뒤에 Base64 문자열이 붙으며, 디코딩하면 JSON이 나옵니다. 아래에는 JSON 내용만 표시했습니다. 도메인과 UUID는 설명용 가상 데이터이므로 연결에 사용할 수 없습니다. v: "2"는 공유 객체의 형식을 나타내며 클라이언트 버전이나 코어 버전을 뜻하지 않습니다.

{
  "v": "2",
  "ps": "VMess WS 예시",
  "add": "edge.example.com",
  "port": "443",
  "id": "00000000-0000-4000-8000-000000000001",
  "aid": "0",
  "scy": "auto",
  "net": "ws",
  "type": "none",
  "host": "edge.example.com",
  "path": "/ws",
  "tls": "tls",
  "sni": "edge.example.com"
}

add는 연결 주소, port는 원격 포트, id는 사용자 식별자입니다. net, host, path는 전송 방식과 WebSocket 요청 설정에 사용됩니다. aid: "0"은 현재 일반적인 VMess AEAD 설정에 해당하므로 오류를 없애려고 다른 숫자로 임의 변경하면 안 됩니다. 예시의 type: "none"도 TLS를 끄는 스위치가 아닙니다.

VLESS 공유 링크는 보통 URI 형식이며, 인증 정보는 @ 앞에, 서버와 포트는 뒤에, 전송 매개변수는 쿼리 문자열에 들어갑니다. 아래 링크도 연결할 수 없는 예시입니다. 링크 전체를 한 줄로 유지하세요.

vless://[email protected]:443?encryption=none&security=tls&type=tcp&sni=edge.example.com#demo-vless

VMess 예시 필드

주소와 포트
add와 port
사용자 식별자
id
전송 방식
net: "ws"
요청 경로
path: "/ws"
TLS 서버 이름
sni

이 필드는 일반적인 공유 객체에 사용되며, 코어 설정으로 변환할 때 키 이름과 계층 구조가 달라집니다.

VLESS 예시 매개변수

사용자 식별자
URI에서 @ 앞에 있는 UUID
프로토콜 암호화
encryption=none
연결 보안
security=tls
전송 방식
type=tcp
TLS 서버 이름
sni=edge.example.com

encryption=none은 TLS를 끈다는 뜻이 아닙니다. 서로 다른 계층의 설정입니다.

URI 매개변수 값은 URL 규칙에 따라 인코딩해야 합니다. 예를 들어 /ws?ed=2048을 하나의 매개변수 값으로 넣으려면 path=%2Fws%3Fed%3D2048로 작성해야 물음표가 별도의 쿼리 문자열로 잘못 해석되지 않습니다. 메모에 포함된 한글, 공백, 특수 기호도 올바르게 인코딩해야 합니다. URI 전체를 한 번에 퍼센트 인코딩하면 프로토콜 접두사와 구분자까지 손상됩니다.

3. Base64 구독을 로컬에서 디코딩하고 다시 인코딩하기

일반적인 변환 과정은 ‘공유 링크 여러 개 → 줄바꿈으로 연결 → 전체 텍스트를 Base64로 인코딩’하는 방식입니다. VMess가 포함된 경우 인코딩이 두 겹으로 적용됩니다. 바깥쪽은 링크 목록 전체를 감싸고, 안쪽은 VMess 링크 하나의 JSON을 감쌉니다. 바깥쪽을 풀었을 때 vmess://가 보이면 줄 단위로 나눠야 합니다. 프로토콜 접두사가 붙은 전체 문자열을 다시 Base64로 취급하지 마세요.

3 → 4
Base64는 구분을 위한 줄바꿈을 제외하면 3바이트당 4개의 문자로 표현됩니다
443
이 글의 예시에서 사용하는 원격 TLS 포트이며 모든 노드에 해당하지 않습니다
10808
아래 설정에서 사용하는 로컬 SOCKS 포트입니다. 사용 중인 포트인지 확인하세요

아래 순수 JavaScript 코드는 로컬의 빈 페이지에서 개발자 도구 콘솔을 열어 실행할 수 있습니다. 먼저 코드를 읽고 자신의 데이터만 로컬 변수에 넣으세요. 네트워크 요청은 발생하지 않습니다. 인코딩 함수는 UTF-8을 사용하므로 한글 메모를 btoa()에 바로 전달할 때 생기는 오류를 방지합니다. 디코딩 함수는 표준 Base64와 흔히 쓰이는 URL 안전 문자 변형을 모두 처리합니다.

function encodeSubscription(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = "";
  for (const byte of bytes) {
    binary += String.fromCharCode(byte);
  }
  return btoa(binary);
}

function decodeSubscription(text) {
  let encoded = text.trim()
    .replace(/\s+/g, "")
    .replace(/-/g, "+")
    .replace(/_/g, "/");

  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encoded)) {
    throw new Error("유효한 Base64 문자로 된 내용이 아닙니다");
  }

  encoded = encoded.replace(/=+$/, "");
  if (encoded.length % 4 === 1) {
    throw new Error("Base64 길이가 올바르지 않습니다. 전체 내용이 복사되었는지 확인하세요");
  }

  encoded += "=".repeat((4 - encoded.length % 4) % 4);
  const bytes = Uint8Array.from(atob(encoded), char => char.charCodeAt(0));
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}

const demoLink =
  "vless://[email protected]:443"
  + "?encryption=none&security=tls&type=tcp&sni=edge.example.com#demo-vless";

const encodedDemo = encodeSubscription(demoLink);
console.log(encodedDemo);
console.log(decodeSubscription(encodedDemo));

4. v2rayN·v2rayNG에서 가져오기

가져오기 메뉴는 ‘무엇을 가지고 있는지’에 따라 선택하세요. 구독 주소는 구독 그룹에 등록하고, 공유 링크 한 개나 여러 개는 클립보드 메뉴로 가져오며, 코어 설정 JSON 전체는 사용자 지정 설정 메뉴로 불러옵니다. 데스크톱과 Android 앱의 메뉴 위치는 버전에 따라 달라질 수 있습니다. 아래에는 흔히 쓰이는 메뉴 이름을 적었으니, 실제 화면의 문구가 조금 다르면 해당 가져오기 기능을 찾아 선택하세요. 구독 주소를 서버 주소 입력란에 넣지 마세요.

v2rayN: 그룹을 만든 뒤 구독 업데이트

  1. 그룹 만들기

    메인 창에서 ‘구독 그룹’ → ‘구독 그룹 설정’을 열고 ‘추가’를 눌러 그룹을 만듭니다. 메모와 구독 주소를 입력하고 저장한 다음 서버 목록으로 돌아갑니다.

  2. 구독 내용 가져오기

    ‘구독 그룹’ 메뉴에서 ‘현재 구독 업데이트(프록시 사용 안 함)’ 또는 프록시를 통한 업데이트 항목을 선택합니다. 현재 네트워크에서 구독 서비스에 직접 접속할 수 있다면 먼저 전자를 사용하세요. 후자를 이용하려면 사용 가능한 프록시와 올바른 프록시 경로가 필요합니다.

  3. 링크 가져오기

    공유 텍스트만 있다면 링크 전체를 복사한 뒤 ‘서버’ → ‘클립보드에서 여러 URL 가져오기’를 선택합니다. 일반적인 데스크톱 화면에서는 Ctrl+V를 사용할 수도 있습니다. 이 방법은 노드 정보를 한 번 가져오는 방식이며 원격 구독 업데이트가 연결되지는 않습니다.

  4. 매개변수 확인

    가져온 항목의 편집 창을 열어 서버 주소, 포트, 전송 프로토콜, TLS, SNI를 확인합니다. 목록에 항목이 보이는 것만으로는 충분하지 않습니다. 해당 항목을 실행하고 창 아래쪽의 코어 로그에 설정 파싱 오류가 없는지도 확인하세요.

v2rayNG: 구독과 클립보드에서 각각 가져오기

구독을 디코딩해 수동으로 가져오면 이후 서버에서 주소, 포트, 인증 정보가 바뀌어도 로컬에 저장된 항목은 자동으로 업데이트되지 않습니다. 계속 업데이트를 받으려면 원래 구독 그룹을 유지하세요. 다른 클라이언트로 옮길 때는 구독 주소와 로컬 사용자 지정 설정을 각각 백업해야 합니다. 공유 링크 목록만 저장해서는 부족합니다.

5. 공유 링크를 Xray 기본 JSON 설정으로 변환하기

클라이언트는 코어 설정을 만들 때 필드 이름만 바꾸지 않습니다. 로컬 인바운드, 아웃바운드 태그, 로그, 라우팅 등 실행에 필요한 정보도 추가합니다. 아래에서는 앞서 본 VLESS TCP + TLS 예시를 최소한의 SOCKS 설정으로 옮깁니다. 로컬에서는 127.0.0.1:10808에만 연결을 허용합니다. 원격 주소는 설명용 도메인이므로 구조를 이해하는 데만 사용하고 실제 연결에는 쓰지 마세요.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "socks-in",
      "listen": "127.0.0.1",
      "port": 10808,
      "protocol": "socks",
      "settings": {
        "auth": "noauth",
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "tag": "proxy",
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "edge.example.com",
            "port": 443,
            "users": [
              {
                "id": "00000000-0000-4000-8000-000000000001",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "serverName": "edge.example.com"
        }
      }
    }
  ]
}
공유 링크의 내용JSON 설정에서 해당하는 위치변환할 때 확인할 사항
서버와 :443settings.vnext[0].address 및 portJSON의 포트 값은 숫자로 입력
UUIDsettings.vnext[0].users[0].id전체 값을 유지해야 하며 메모로 대체할 수 없음
encryption=noneusers[0].encryptionTLS 설정이 아닌 VLESS 사용자 설정에 포함
type=tcpstreamSettings.network현재 코어가 해당 전송 이름과 문법을 지원해야 함
security=tls 및 snistreamSettings.security 및 tlsSettings.serverName서버 IP만 보고 SNI를 추측하면 안 됨
#demo-vless일반적으로 클라이언트 메모에 저장메모가 실행 설정에 포함되지 않을 수 있음

로컬 포트 10808은 이 예시에서 명시적으로 지정한 값입니다. 모든 클라이언트가 현재 이 포트를 사용한다고 볼 수는 없습니다. 실행하기 전에 다른 프로그램이 해당 포트를 사용 중인지 확인하세요. 루프백 주소에만 바인딩하면 인증이 설정되지 않은 SOCKS 인바운드가 로컬 네트워크에 노출되는 것을 막을 수 있습니다. 원격 연결 문제를 해결하겠다며 모든 네트워크 인터페이스에서 수신하도록 변경하지 마세요.

전체 JSON을 공유 링크로 내보내면 일반적으로 호환되는 프록시 아웃바운드 하나만 추출할 수 있습니다. routing.rules, DNS, 여러 인바운드와 아웃바운드 사이의 조합 관계는 단일 노드 공유 링크로 온전히 표현할 수 없습니다. 원래 설정이 이런 항목에 의존한다면 JSON을 계속 보관하세요. QR 코드 하나를 내보냈다고 전체 설정이 완전히 이전된 것은 아닙니다.

6. 가져온 목록이 비어 있거나 디코딩 오류가 나거나 변환 후 필드가 사라질 때

문제를 찾을 때는 ‘가져오기 → 디코딩 → 파싱 → 실행’ 단계별로 결과를 기록하세요. 구독 업데이트가 성공했다는 것은 클라이언트가 어떤 처리를 완료했다는 뜻일 뿐입니다. 항목이 생성되었는지, 필드가 모두 들어왔는지, 코어가 시작되었는지, 원격 서버에 접속할 수 있는지는 각각 별도로 확인해야 합니다. 가장 먼저 실패한 단계부터 바로잡으세요. TLS, 포트, 구독 주소를 동시에 바꾸면 비교하기 어려워집니다.

구독 주소는 열리는데 가져온 뒤 목록이 비어 있는 이유는 무엇인가요?

요청이 성공했는지만 보지 말고 실제 응답 내용을 확인하세요. 응답이 웹페이지나 권한 안내이거나 현재 클라이언트에서 지원하지 않는 형식이라면 구독 제공업체에 지원되는 형식을 문의하세요. 디코딩 결과가 공유 링크라면 링크 하나를 전체 복사해 클립보드 가져오기 메뉴로 테스트하세요. 그러면 응답 형식 문제와 단일 링크 문제를 구분할 수 있습니다.

Base64 디코딩에서 문자나 길이가 잘못되었다고 나올 때는 어떻게 하나요?

먼저 구독 URL 자체를 디코딩하려는 것은 아닌지 확인하고, 따옴표나 코드 블록 표시, 줄임표가 포함되지 않았는지도 살펴보세요. 본문 안의 구분용 공백을 제거하고 URL 안전 문자와 끝부분 패딩을 확인합니다. 내용이 잘렸다면 등호를 몇 개 붙여도 누락된 데이터는 복원되지 않습니다. 전체 내용을 다시 가져오세요.

JSON 문법 검사를 통과했는데 코어에서 오류가 나는 이유는 무엇인가요?

문법 검사를 통과했다는 것은 쉼표, 따옴표, 괄호가 올바르다는 뜻이지 코어에서 필드 구조를 지원한다는 뜻은 아닙니다. 먼저 Xray 실행 설정인지 VMess 공유 객체인지 확인하세요. 그런 다음 코어 로그가 가리키는 필드, 전송 유형, 누락된 파일을 살펴보고 가져온 설정이 실제 코어와 호환되는지 확인합니다.

내보낸 뒤 다시 가져왔는데 노드 이름은 같은데 연결되지 않는 이유는 무엇인가요?

이름은 연결 매개변수가 아닙니다. 주소, 포트, UUID, 전송 방식, 경로, Host, SNI를 하나씩 비교하고, 해당하는 경우 Flow와 REALITY 매개변수도 확인하세요. 인증서 검증을 끄는 것으로 확인 절차를 대신하지 마세요. 내보내기 도구가 필수 필드를 누락했다면 전체 설정을 백업하거나 원래 구독에서 복원하세요.

로컬에서 수정한 노드가 구독 업데이트 후 원래대로 돌아가는 이유는 무엇인가요?

구독 업데이트는 보통 원격 내용에 따라 해당 항목을 새로 고치므로 로컬 수정 사항이 유지되지 않을 수 있습니다. 별도로 관리할 항목은 구독 그룹이 아닌 곳으로 복사하고 변경 사항을 기록하세요. 서버에서 계속 관리하는 노드는 구독 원본에서 수정하는 것이 좋습니다. 그래야 업데이트할 때마다 다시 손볼 필요가 없습니다.

7. 변환 결과 확인과 백업 순서

안전한 이전을 위해서는 ‘길어 보이는 문자열 하나’가 아니라 용도가 분명한 세 가지 데이터를 보관해야 합니다. 구독 주소는 이후 업데이트에, 공유 링크는 노드 전달에, 전체 설정 파일이나 클라이언트 백업은 로컬 동작 복원에 사용합니다. 세 가지를 함께 보관할 수는 있지만 서로 대신할 수는 없습니다. 인증 정보가 들어 있는 백업은 접근이 제한된 로컬 위치에 보관하세요.

  1. 원본 보관: 변환하기 전에 원래 응답이나 파일을 저장해 두세요. 여러 차례 처리한 결과만 남기지 마세요.
  2. 단일 항목 확인: 먼저 항목 하나를 골라 프로토콜, 주소, 포트, 전송 매개변수를 비교하세요. UUID, Host, SNI를 서로 혼동하지 않았는지도 확인합니다.
  3. 실행 확인: 항목을 활성화하고 코어 로그를 확인하세요. 설정 파싱 오류와 포트 사용 문제를 먼저 해결한 뒤 인증서, 핸드셰이크, 원격 연결 문제를 살펴봅니다.
  4. 앱 연결 확인: 데스크톱에서는 시스템 프록시나 지정한 로컬 프록시 포트가 올바르게 연결되어야 합니다. Android에서는 연결을 시작하고 시스템에서 요구하는 VPN 권한을 허용했는지 확인하세요.
  5. 업데이트 확인: 구독을 이전하려는 경우 대상 클라이언트에서 구독을 수동으로 한 번 업데이트하세요. 새 항목이 예상한 그룹에 들어왔는지 확인합니다. 고정된 노드 목록만 추가된 것은 아닌지 살펴보세요.