구독 주소는 설정을 가져오는 경로이고, Base64는 텍스트 인코딩 방식, vmess://는 노드 공유 형식, JSON은 코어의 실행 설정을 기술하는 형식입니다. 여러 기기에서 노드를 가져오거나 구독 내용을 확인하고 사용자 지정 설정을 옮기려는 경우에 유용합니다. 먼저 응답 형식을 확인하고 로컬에서 디코딩한 뒤 프로토콜, 전송 방식, TLS 필드를 점검하세요. 파싱에 성공했다고 설정까지 완전한 것은 아닙니다.
구독 주소, 노드 목록, JSON 설정 구분하기
‘V2Ray 구독’은 하나의 통일된 파일 형식을 뜻하지 않습니다. HTTPS 주소에 접속하면 Base64로 인코딩된 공유 링크 목록, 일반 텍스트 목록, 전체 JSON 또는 로그인 페이지가 반환될 수 있습니다. 파일 확장자나 주소에 ‘subscribe’가 포함됐는지는 응답 내용을 판단하는 기준이 아닙니다. 클라이언트는 서버가 반환한 응답 본문을 처리합니다.
일반적인 Base64 구독은 두 단계로 구성됩니다. 먼저 목록 전체를 한 번 인코딩하고, 디코딩한 결과의 각 줄에는 공유 링크가 하나씩 들어 있습니다. 그중 전통적인 JSON 형식의 vmess:// 링크가 있다면, 프로토콜 접두사 뒤의 페이로드도 다시 Base64로 인코딩되어 있습니다. 두 인코딩은 대상이 다르므로 구독 전체를 단일 VMess 노드로 파싱하면 안 됩니다.
공유 링크 목록 구독
권장일반적인 노드 관리에 여러 공유 링크를 제공합니다. 서비스 제공자가 주소나 포트를 변경하면 클라이언트에서 구독을 업데이트해 목록을 다시 가져올 수 있습니다. 바깥쪽 목록을 Base64로 인코딩하는지는 제공 방식에 따라 다릅니다.
적합한 용도: v2rayN과 v2rayNG에서 같은 출처의 노드 관리
단일 vmess:// 공유 링크
VMess 노드 하나의 연결 매개변수를 담으며, 지속적으로 업데이트되는 구독 주소와는 다릅니다. 보통 클립보드나 QR 코드로 가져옵니다.
적합한 용도: 노드 하나를 임시로 옮기거나 가져오기 문제를 필드별로 점검할 때
코어용 JSON 설정
인바운드, 여러 아웃바운드, DNS, 라우팅 등을 포함할 수 있습니다. 단순한 노드 목록이 아니며 실제 사용하는 코어의 설정 형식과 호환되어야 합니다.
적합한 용도: 인바운드와 라우팅을 직접 제어하는 사용자 지정 설정
v2rayN과 v2rayNG에서 가져오기 메뉴 선택하기
먼저 내용에 맞는 가져오기 메뉴를 선택한 다음 변환 여부를 검토하세요. HTTPS 구독 주소를 클립보드에서 노드로 가져오거나 전체 JSON을 구독 주소 입력란에 붙여 넣으면 원하는 결과를 얻기 어렵습니다. 아래 메뉴 이름은 일반적인 중국어 UI를 기준으로 했으며, 버전에 따라 표현이 조금씩 다를 수 있으니 기능을 확인하세요.
두 클라이언트에서 같은 공유 링크 구독 사용하기
데스크톱: v2rayN
- 「구독 그룹」→「구독 그룹 설정」을 열고 그룹을 추가한 다음 구독 주소를 입력합니다.
- 저장한 뒤 「구독 그룹」 메뉴에서 「모든 구독 업데이트(프록시 사용 안 함)」를 실행합니다. 해당 주소에 프록시 접속이 필요하면 프록시를 통한 업데이트를 선택하세요.
- 공유 링크가 하나뿐이라면 「서버」→「클립보드에서 공유 링크 가져오기」를 사용하세요. 주소가 vmess://인 구독 그룹을 새로 만들지 마세요.
Android: v2rayNG
- 사이드 메뉴에서 「구독 그룹 설정」을 열고 추가 버튼을 누른 뒤 메모와 구독 URL을 입력해 저장합니다.
- 메인 화면으로 돌아와 오른쪽 위 메뉴에서 「구독 업데이트」를 실행합니다. 목록에 해당 그룹의 노드가 표시되는지 확인하세요.
- 공유 링크 하나는 메인 화면의 「+」→「클립보드에서 가져오기」를 사용하세요. 전체 설정은 사용자 지정 설정의 파일 또는 클립보드 가져오기 메뉴를 이용합니다.
같은 출처의 설정이라도 두 클라이언트가 모든 매개변수를 똑같이 지원하는 것은 아닙니다. 업데이트한 뒤 노드 수, 전송 방식, TLS 설정을 확인하세요.
두 클라이언트 모두 일반적인 전통적 JSON 형식의 VMess 공유 링크와 공유 링크 목록 구독을 처리할 수 있습니다. 전체 JSON 설정은 사용자 지정 설정 메뉴로 가져와야 합니다. VMess를 지원한다고 해서 모든 JSON 구독 형식도 지원한다고 볼 수는 없습니다. 코어 전용 필드가 포함된 설정은 클라이언트 버전과 선택한 코어의 호환성을 함께 확인하세요.
v2rayNG는 Xray 코어를, v2flyNG는 v2fly 코어를 사용합니다. 두 앱은 화면만 다른 동일한 설정 해석기가 아닙니다. Xray 전용 설정을 v2flyNG에서 사용하기 전에 파일 이름만 바꾸지 말고 프로토콜과 필드의 지원 여부를 확인하세요. v2rayN에서는 해당 설정이 실제로 어떤 코어에서 실행되는지도 확인해야 합니다.
로컬에서 디코딩하기: 목록을 확인한 다음 vmess:// 페이로드 살펴보기
아래 JavaScript는 로컬 브라우저의 개발자 도구에서 텍스트를 확인하기 위한 코드이며 네트워크 요청을 보내지 않습니다. 코드를 먼저 읽고 오프라인 빈 페이지의 콘솔에서 실행하세요. 함수에는 로컬에 복사해 둔 본문을 전달하며 구독을 다운로드하지 않습니다. 출처를 알 수 없는 웹페이지의 콘솔에서는 인증 정보를 처리하지 마세요.
function decodeBase64Utf8(input) {
let text = input.trim().replace(/\s+/g, "");
text = text.replace(/-/g, "+").replace(/_/g, "/");
if (text.length % 4 === 1) {
throw new Error("Base64 길이가 올바르지 않습니다");
}
text = text.padEnd(Math.ceil(text.length / 4) * 4, "=");
const bytes = Uint8Array.from(atob(text), ch => ch.charCodeAt(0));
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}
function encodeBase64Utf8(text) {
const bytes = new TextEncoder().encode(text);
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
function decodeVmessJson(uri) {
const text = uri.trim();
if (!text.startsWith("vmess://")) {
throw new Error("VMess 공유 링크가 아닙니다");
}
return JSON.parse(decodeBase64Utf8(text.slice(8)));
}
// 자체 테스트에는 로컬 예시만 사용하며 구독 인증 정보는 포함하지 않습니다.
console.log(decodeBase64Utf8(encodeBase64Utf8("노드 예시")));
- 응답 확인: 본문에
vmess://같은 프로토콜 접두사가 줄별로 이미 표시되어 있다면 각 줄을 그대로 처리하고 전체를 다시 디코딩하지 마세요. HTML 태그나 로그인 안내로 시작한다면 먼저 구독 요청 문제를 해결해야 합니다. - 바깥쪽 디코딩: Base64 목록으로 확인된 본문에
decodeBase64Utf8를 적용한 뒤split(/\r?\n/)으로 줄을 나누고 빈 줄을 제거합니다. - 링크 하나 디코딩: 전통적인 JSON 형식의 VMess 링크 하나를
decodeVmessJson에 전달합니다. 결과는add,port,id등의 필드를 포함하는 객체여야 합니다. - 무작정 재시도하지 마세요: 디코딩 결과가 JSON이 아니라면 공유 형식의 변형 여부를 확인하세요. 같은 프로토콜 접두사를 쓰는 과거 구현이 모두 이 문서의 JSON 페이로드 규칙을 따르는 것은 아닙니다.
atob는 바이트에 해당하는 문자열을 반환할 뿐 UTF-8 디코딩까지 처리하지 않습니다. TextDecoder를 생략하면 노드 이름의 한글이 깨질 수 있으며, 한글에 btoa를 직접 사용하면 오류가 발생할 수 있습니다. Base64URL의 -, _와 표준 Base64의 +, /도 구분해야 합니다. 구독 URL 자체에 이 치환을 적용하면 안 됩니다.
vmess://와 JSON 설정의 필드 대응 관계
다음은 학습용 전통적 VMess 공유 페이로드입니다. 도메인과 UUID는 구조를 보여 주기 위한 예시이며 실제로 연결되는 서비스 정보가 아닙니다. v는 공유 형식의 버전이지 코어 버전이 아닙니다. aid는 alterId를 뜻하며 최신 VMess AEAD 설정에서는 보통 0을 사용하지만, 설정을 옮길 때는 서버 요구 사항을 우선 확인하세요.
{
"v": "2",
"ps": "예시 VMess WS TLS",
"add": "node.example.com",
"port": "443",
"id": "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
"aid": "0",
"scy": "auto",
"net": "ws",
"type": "none",
"host": "node.example.com",
"path": "/gateway",
"tls": "tls",
"sni": "node.example.com"
}
| 공유 필드 | 일반적인 코어 설정 위치 | 변환 시 주의 사항 |
|---|---|---|
add / port | settings.vnext[].address / port | 공유 링크에서는 포트가 문자열인 경우가 많지만 코어 설정에서는 숫자로 사용합니다. |
id / aid / scy | users[].id / alterId / security | 각각 UUID, alterId, VMess 콘텐츠 암호화 방식을 나타냅니다. |
net | streamSettings.network | 이 예시는 ws입니다. 이름만 바꿔 다른 전송 방식으로 변환할 수 없습니다. |
host / path | wsSettings.headers.Host / path | WebSocket 요청 매개변수이며 서버 주소와 항상 같은 값은 아닙니다. |
tls / sni | streamSettings.security / tlsSettings.serverName | TLS와 VMess 콘텐츠 암호화는 서로 다른 설정입니다. |
ps | 클라이언트 메모 또는 사용자 지정 아웃바운드 태그 | 메모는 연결 매개변수가 아니며 라우팅 규칙을 자동으로 만들지도 않습니다. |
아래의 전체 예시는 일반적인 vnext 구조를 사용하며 로컬 SOCKS 인바운드 하나와 VMess 아웃바운드 하나만 표시합니다. 클라이언트로 가져온 뒤 인바운드 설정이 유지되는지는 사용자 지정 설정의 실행 방식에 따라 다릅니다. 실행 전에 현재 코어가 이 필드 구조를 지원하는지도 확인하세요.
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"tag": "local-socks",
"listen": "127.0.0.1",
"port": 10808,
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "node.example.com",
"port": 443,
"users": [
{
"id": "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
"alterId": 0,
"security": "auto"
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": {
"serverName": "node.example.com",
"allowInsecure": false
},
"wsSettings": {
"path": "/gateway",
"headers": {
"Host": "node.example.com"
}
}
}
}
]
}
다시 인코딩하고 역변환하기: 변환 과정에서 사라질 수 있는 정보
전통적인 JSON 형식의 VMess 페이로드에서 공유 링크를 만들려면 JSON을 UTF-8로 인코딩한 다음 프로토콜 접두사를 붙이면 됩니다. 여러 링크를 줄바꿈으로 연결해 전체를 인코딩하면 일반적인 Base64 목록 본문이 됩니다. 아래 코드는 앞에서 정의한 인코딩 함수를 이어서 사용하며 입력은 학습용 예시입니다.
const sample = {
v: "2",
ps: "예시 VMess WS TLS",
add: "node.example.com",
port: "443",
id: "8a7e4f30-5b91-4f8e-9e42-2c6d1b0a73f5",
aid: "0",
scy: "auto",
net: "ws",
type: "none",
host: "node.example.com",
path: "/gateway",
tls: "tls",
sni: "node.example.com"
};
const share = "vmess://" + encodeBase64Utf8(JSON.stringify(sample));
const links = [share];
const subscriptionBody = encodeBase64Utf8(links.join("\n"));
console.log(share);
console.log(subscriptionBody);
목록 본문을 만든 것만으로 구독 서비스가 생기지는 않습니다. 클라이언트가 접속할 수 있는 주소에 내용을 올리고 적절한 접근 제어를 설정해야 업데이트 가능한 원격 경로가 됩니다. 기기 간에만 옮길 목적이라면 클라이언트에서 공유 링크를 내보내면 충분하며 파일 서버를 별도로 운영할 필요가 없습니다.
- 전체 설정을 단일 노드로 바꾸면 설정 맥락이 사라집니다: DNS, 분기 라우팅 규칙, 로컬 수신 포트, 여러 아웃바운드의 관계와 아웃바운드 체인은 일반 VMess 공유 링크 하나에 온전히 담을 수 없습니다.
- JSON에 사용할 수 있는 아웃바운드가 여러 개일 수 있습니다:
protocol과tag를 기준으로 대상 VMess 아웃바운드를 찾으세요. 배열의 첫 항목이 공유할 노드라고 단정하지 마세요. - 전송 확장 정보를 손실 없이 표현하지 못할 수 있습니다: 추가 TLS 또는 전송 매개변수가 있다면 수신 클라이언트가 해당 공유 필드를 지원하는지 확인하세요. 내보낸 뒤 임시 설정으로 다시 가져와 항목별로 비교하세요.
- 프로토콜 변경은 인코딩 변환이 아닙니다: 접두사를 VLESS나 Trojan으로 바꾼다고 VMess가 다른 종류의 노드로 바뀌지는 않습니다. 서버가 해당 프로토콜과 인증 정보를 제공해야 합니다.
가져오기 오류, 노드 0개, 한글 깨짐 문제 해결
구독 업데이트가 끝났는데 노드가 왜 0개인가요?
올바른 그룹을 보고 있는지 먼저 확인하고 업데이트 로그의 파싱 안내를 살펴보세요. 응답 본문을 로컬에서 확인했을 때 로그인 페이지, 만료된 토큰 안내 또는 지원하지 않는 설정 구조가 표시된다면 Base64를 반복해서 디코딩해도 해결되지 않습니다. v2rayN·v2rayNG에 사용할 수 있는 공유 링크 목록 형식을 제공해 달라고 구독 제공자에게 요청하세요.
디코딩 오류가 발생합니다. 일부 내용을 복사하지 못한 걸까요?
메신저에서 텍스트가 줄임표로 바뀌지 않았는지, 따옴표가 섞이지 않았는지, 길이를 4로 나눈 나머지가 1인지 확인하세요. 끝부분의 패딩 기호가 빠졌다면 보정할 수 있지만 잘린 본문은 등호를 덧붙여 복구할 수 없습니다. 전체 HTTPS 구독 주소에 디코딩 함수를 사용하지 마세요.
노드 이름이 깨졌는데 연결 매개변수는 사용할 수 있나요?
먼저 UTF-8로 다시 디코딩하고 페이로드의 ps 값을 확인하세요. 메모만 깨진 경우에는 보통 연결에 직접 영향을 주지 않습니다. 경로나 다른 필드에 ASCII가 아닌 문자가 있다면 항목별로 확인해야 합니다. 원본 텍스트를 보관하고 깨진 결과를 반복해서 재인코딩하지 마세요.
가져오기는 성공했지만 연결 시간이 초과됩니다. 형식을 다시 변환해야 하나요?
먼저 서버 주소와 443 포트가 서비스 제공자가 안내한 값인지 확인한 다음 UUID, WS 경로, Host, SNI를 점검하세요. 로그에 10808 포트가 사용 중이라고 나오면 로컬 인바운드 충돌을 해결해야 합니다. TLS 이름 오류라면 서버 이름을 수정하세요. 같은 내용을 반복해서 인코딩할 필요는 없습니다.
직접 수정한 노드가 구독 업데이트 후 원래대로 돌아오는 이유는 무엇인가요?
구독 노드는 보통 원격 콘텐츠를 바탕으로 다시 생성됩니다. 로컬 변경 사항을 유지하려면 먼저 별도 설정으로 내보내거나 복사한 뒤 필드를 수정하세요. 변경 내용이 유지되는 방식은 클라이언트 버전에 따라 다릅니다. 구독 업데이트는 출처의 데이터를 가져오며 로컬 편집 내용을 서비스 제공자에게 자동으로 동기화하지 않습니다.
안정적으로 설정을 옮기려면 원본 구독 본문, 디코딩한 노드 필드, 가져온 뒤의 클라이언트 설정을 각각 보관해 비교할 수 있어야 합니다. 먼저 노드 하나로 연결을 확인한 뒤 목록 전체를 적용하세요. 전체 JSON의 DNS와 라우팅을 옮길 때는 고급 설정을 참고해 항목별로 확인하세요. 필드의 의미가 익숙하지 않다면 여러 변환 도구를 연달아 시도하기보다 용어집을 먼저 살펴보는 편이 차이를 찾기 쉽습니다.