この記事の概要

サブスクリプションURLは設定を取得するための入口、Base64はテキストのエンコード方式、共有リンクはノード情報、ネイティブJSONはコア全体の実行設定を表す場合があります。「URLは開けるのにインポートできない」「ノードを移行したい」といった場合は、まずレスポンスの内容を見分け、ローカルでデコードしてからプロトコルの項目を確認しましょう。変換で移行できるのはノードのパラメーターであり、DNSやルーティング、更新設定まで引き継げるとは限りません。

サブスクリプションURL・レスポンス本文・ノード設定を区別する

https://sub.example.com/list?token=demoのようなURLは、クライアントがコンテンツを取得するためのものです。URLだけでは、返される形式を判断できません。この記事に登場するドメイン、UUID、パラメーターは構造を示す例であり、接続には使えません。実際のサブスクリプションURLに含まれるトークンにはアクセス権限があるため、URLを共有するとノード一式が漏えいするおそれがあります。

従来のV2Rayサブスクリプションでは、複数の共有リンクを改行でつなぎ、テキスト全体をBase64でエンコードする形式が一般的です。エンコードせずリンク一覧を返すサービスや、クライアントのリクエストに応じて異なる内容を返すサービスもあります。そのため、ブラウザーで開いたときとクライアントで更新したときに、同じ内容が返るとは限りません。

サブスクリプションURLとリンク一覧

おすすめ

日常的に複数のノードを管理するなら、元のサブスクリプションURLを使うのがおすすめです。サーバー側で一覧を更新でき、クライアントはグループ単位で取得できます。本文はBase64の場合も、プレーンテキストのリンク一覧の場合もあります。

向いている用途:ノードを継続的に更新する、デスクトップとAndroidでそれぞれインポートする

単一ノードの共有リンク

よく使われるプレフィックスはvmess://またはvless://です。リンクに含まれるのは主にノードの接続パラメーターで、実行設定全体ではありません。

向いている用途:ノードを1つだけ一時的に共有する、特定のレコードの項目を確認する

コアのネイティブJSON

インバウンド、アウトバウンド、DNS、ルーティングを含められます。実行できるかどうかは、JSONという拡張子ではなく、対象コアと設定の構造によって決まります。

向いている用途:インバウンド、アウトバウンド、ルーティングをカスタマイズする設定管理者

v2rayNとv2rayNGのインポート方法

どちらのクライアントも、一般的なVMess・VLESS共有リンクや従来型のリンクサブスクリプションを管理できます。ただし、テキストを認識できても、対象コアがすべてのパラメーターに対応しているとは限りません。画面上の名称はバージョンによって異なるため、ここでは機能ごとに説明します。サブスクリプションURLを単一ノード用の入力欄に貼り付けないよう注意してください。

入力内容に応じたインポート方法
入力内容v2rayN(デスクトップ)v2rayNG(Android)
HTTPSサブスクリプションURL「サブスクリプショングループ」→「サブスクリプショングループ設定」を開き、グループを追加してURLを入力し、そのグループを更新「サブスクリプショングループ設定」でURLを追加し、一覧に戻って「サブスクリプションを更新」を実行
プレーンテキストの共有リンクリンク全体をコピーし、「クリップボードから複数のURLをインポート」などの機能を使用右上の「+」→「クリップボードからインポート」
Base64形式のテキスト全体まずサブスクリプション更新機能で解析します。手動で移行する場合は、先にデコードしてからリンク一覧をインポートします。サブスクリプショングループの利用をおすすめします。手動インポートで認識されない場合は、共有リンクにデコードしてから試してください。
コアのネイティブJSONカスタム設定用の機能から、対象コアに合った設定を選んでください。通常のサブスクリプションURLとしては扱えません。カスタム設定のインポート機能を使用します。対応範囲は使用するXrayコアとクライアントのバージョンによって異なります。

v2rayNGはXrayコア、v2flyNGはV2Flyコアを使用します。JSONの構文が正しくても、両方のコアが含まれるセキュリティ機能や設定項目に対応しているとは限りません。特にVLESSやREALITYを組み合わせる場合は、形式を変換する前に対象コアの対応状況を確認してください。形式の変換によってプロトコルへの対応が追加されることはありません。

Base64サブスクリプションのデコード:まず外側を確認し、次に各行を見る

最初に、エラーページではなくレスポンス本文を取得できているか確認します。本文が<!DOCTYPE html>、ログイン案内、APIエラーなどで始まる場合、デコードするサブスクリプションではありません。ファイルの拡張子を変えたり、何度もインポートしたりしても、認証切れやサーバー側のエラーは解決できません。

  1. レスポンス本文をローカルのテキストファイルに保存し、元の内容も残しておきます。コピー時に混入した説明文は削除し、URLをBase64の本文として扱わないでください。
  2. 本文がすでにvmess://またはvless://で始まっている場合は、プレーンテキストのリンク一覧として扱い、本文全体をデコードしないでください。
  3. 本文が長いエンコード文字列の場合は、外側のBase64を1回デコードし、改行で分割して空行を削除します。
  4. 各行のプロトコルプレフィックスを確認します。行がvmess://で始まる場合、その後ろにVMess共有情報のエンコードが含まれることがあります。だからといって、サブスクリプション全体を何度もデコードする必要はありません。

以下のコードは、ローカルの空白ページで開いた開発者ツールのコンソールを使い、取得済みのテキストを処理するためのものです。サブスクリプションURLへのリクエストは行いません。まず関数を定義し、次にdecodeSubscription(prompt("サブスクリプションのレスポンス本文を貼り付け"))を実行します。信頼できないWebページのコンソールで、実際の認証情報を扱わないでください。

function decodeSubscription(input) {
  const raw = String(input ?? "").trim();
  if (!raw) throw new Error("本文が空です");
  if (/^(vmess|vless):\/\//i.test(raw)) return raw;

  let encoded = raw.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("エンコード長が不正です。すべてコピーできているか確認してください");
  }
  encoded += "=".repeat((4 - encoded.length % 4) % 4);
  const bytes = Uint8Array.from(atob(encoded), c => c.charCodeAt(0));
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
}

共有リンクをBase64に変換:改行でつないでからエンコード

プレーンテキストのリンク一覧を従来型のBase64サブスクリプション本文にする手順は、「1行に1つのリンク → UTF-8バイト列 → Base64」です。生成されるのは静的な本文であり、更新可能なサブスクリプションサービスが自動で作られるわけではありません。結果をローカルファイルに保存しても、クライアントがアクセスできるサブスクリプションURLができたことにはなりません。

const link = "vless://[email protected]:443?encryption=none&security=tls&type=tcp&sni=node.example.com#Example-TLS";

function encodeSubscription(links) {
  const text = links.map(item => item.trim()).filter(Boolean).join("\n");
  const bytes = new TextEncoder().encode(text);
  let binary = "";
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return btoa(binary);
}

const encoded = encodeSubscription([link]);
console.log(encoded);
console.log(decodeSubscription(encoded) === link);

日本語の備考もエンコード対象になるため、日本語を含む文字列にそのままbtoaを使うのは避けてください。先にTextEncoderでUTF-8バイト列へ変換すれば、扱える文字の範囲を超えるエラーを防げます。最後の往復比較でtrueが返れば、例のテキストがエンコード処理で変化していないことを確認できます。ノードに接続できるかどうかを検証するものではありません。

443
例として使用するリモートTLSポート
10808
後述のローカルSOCKSポートの例
3 → 4
Base64は3バイトを4文字にエンコード

上記の数値はプロトコルのエンコード規則と設定例であり、速度測定の結果でも、すべてのクライアントバージョンの既定値でもありません。Base64は圧縮ではありません。末尾のパディングを除くと、エンコード後の長さは元のバイト数の約4/3、つまり約3分の1増えます。

共有リンクとネイティブJSON:項目の対応付けだけでは完全に移行できない

先ほどのVLESSリンクを例にすると、ホストとポートはアウトバウンドのサーバー設定、UUIDはユーザー設定に入り、security=tlsはTLS、sniはTLSのサーバー名に対応します。以下はXray形式の単一アウトバウンドオブジェクトであり、そのまま実行できる完全な設定ファイルではありません。完全な設定のoutbounds配列に追加する必要があります。

{
  "tag": "proxy",
  "protocol": "vless",
  "settings": {
    "vnext": [
      {
        "address": "node.example.com",
        "port": 443,
        "users": [
          {
            "id": "11111111-1111-4111-8111-111111111111",
            "encryption": "none"
          }
        ]
      }
    ]
  },
  "streamSettings": {
    "network": "tcp",
    "security": "tls",
    "tlsSettings": {
      "serverName": "node.example.com"
    }
  }
}

実行可能な設定にするには、ローカルのインバウンド、アウトバウンドの選択、必要なルーティング設定も指定します。たとえば、ローカルのSOCKSリスナーが127.0.0.1:10808の場合、それを使うアプリにも同じアドレスとポートを設定します。リモート側の443をローカルのプロキシポートに設定してはいけません。両者は通信経路の別々の端にあります。

変換時に項目ごとに確認するポイント
共有リンクの内容ネイティブ設定での対応箇所見落としやすい点
ホスト、ポート、UUIDsettings.vnext内のサーバーとユーザーポートは数値で指定し、UUIDの前後に空白を入れない
typeとトランスポートのパラメーターstreamSettingsWebSocketではパスとHostの両方を移行する
security、sniTLSまたは対応するセキュリティ設定サーバーアドレスとSNIは異なる場合があるため、同じ値に変更しない
flowとREALITYのパラメーターユーザー項目と対応するセキュリティ設定サーバーで必要とされるflow、公開鍵、short IDなどを保持する
#以降の備考クライアント上の表示名または管理用ラベル備考は認証情報ではなく、自動的にルーティングルールにもなりません

VMessでは、vmess://の後ろに共有オブジェクトをBase64エンコードして付ける形式が一般的です。オブジェクトのadd、port、id、net、pathなどは共有リンク用の項目であり、コアのネイティブ設定におけるトップレベル項目ではありません。デコードしたオブジェクトをそのまま実行設定として保存すると、構造が合わず失敗することがほとんどです。

移行先は2種類:ノードのパラメーターと完全な設定を分けて扱う

ノードだけを移行する
  • まずクライアントから共有リンクをエクスポートし、移行先のクライアントにインポートします。
  • UUID、トランスポート、TLS、SNI、パスなどの接続項目を確認します。
  • 移行先のクライアントでシステムプロキシ、DNS、ルーティングを設定し直します。
実行設定全体を保持する
  • JSON内のインバウンド、アウトバウンドのタグとルーティングの参照関係を保持します。
  • 移行先のコアがすべての項目に対応していることを確認し、ローカルのリスナーポートが競合していないか調べます。
  • カスタム設定のインポート機能を使い、無理に単一ノードのリンクへ変換しないでください。

ネイティブJSONを共有リンクに戻す場合、通常は一部のアウトバウンドしか抽出できません。複数のアウトバウンドをつなぐ構成、DNS、ルーティングルールを、汎用の単一リンクで損失なく表現する方法はありません。

変換後の確認:更新成功と接続成功を分けて確認する

サブスクリプションの更新成功は、クライアントが内容の一部または全部を取得・解析できたことを示すだけで、すべてのノードが使えるという意味ではありません。元のサブスクリプショングループを残し、変換結果は別のテスト用グループにインポートしましょう。まず1つのノードを確認してから、まとめて処理します。手動でエクスポートした静的リンクには、元のサブスクリプションの更新設定は引き継がれません。

  1. レコードを確認:変換前後のノード数を比較し、空行、重複レコード、非対応プロトコルのスキップがないか確認します。
  2. 項目を確認:ノードの編集画面で、アドレス、ポート、UUID、トランスポート、パス、SNI、セキュリティ方式を確認します。備考が同じかどうかだけで判断しないでください。
  3. 動作を確認:テストするノードを選び、コアのログを確認します。設定の解析エラー、認証失敗、接続タイムアウトは、それぞれ分けて対処してください。
  4. アプリの接続先を確認:システムプロキシまたはアプリのプロキシが、実際のローカルリスナーポートを参照していることを確認します。次に、テストリクエストが想定したアウトバウンドへ送られるよう、ルーティングを確認します。

デコードしても長い文字列のままです。もう一度デコードする必要がありますか?

まずvmess://が現れているか確認します。含まれている場合は、その行の後半部分だけをVMess共有形式として処理し、一覧全体を続けてデコードしないでください。プロトコルプレフィックスも判別できるJSON構造もない場合は、元のレスポンスが完全か、サーバーが実際にどの形式を返しているかを確認します。

JSONを開けるのに、クライアントでインポートに失敗するのはなぜですか?

トップレベルの項目を確認します。add、idなどだけを含むオブジェクトは、VMessの共有情報である可能性があります。inbounds、outboundsを含むものは、コアの実行設定である可能性があります。後者はカスタム設定のインポート機能を使い、対象コアが各項目に対応しているかも確認してください。

同じサブスクリプションがデスクトップでは更新できるのに、Androidでは何もインポートされないのはなぜですか?

まず両方の端末で、サブスクリプションURLが完全か確認します。次に、クライアントからのリクエストによってレスポンスが変わっていないか調べます。レスポンスが正常なら、プロトコルとトランスポートの対応状況を比較してください。プレーンテキストのリンクを1つだけ「+」→「クリップボードからインポート」で試すと、サブスクリプション全体を何度も変換せずに原因を絞り込めます。

JSON全体を1つのVLESSリンクに変換できますか?

リンクで表現できるVLESSアウトバウンドが1つだけの場合に限り、対応する項目を取り出してリンクを生成できます。まずprotocolがvlessのアウトバウンドを見つけ、その接続パラメーターを保持します。インバウンド、DNS、ルーティング、その他のアウトバウンドは別途保存する必要があり、一度の変換ですべてを保持できるとは限りません。