SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-28

wg-easyをDockerで構築する方法とv15の注意点

Docker Composeでwg-easyを構築する手順です。必要なポート、NET_ADMIN、sysctls、VPSの条件、v15で環境変数が無効になった変更、スマートフォン向けQRコード接続を説明します。

構築するもの

wg-easy は、1 つの Docker コンテナとして動作する Web インターフェース付きの WireGuard です。WireGuard インターフェースを管理し、クライアントを作成するためのブラウザー UI を追加します。作成したクライアントごとに設定ファイルと QR コードが生成されるため、スマートフォンは画面にカメラを向けるだけで VPN に接続できます。

トンネル自体は通常の WireGuard です。パケットはカーネルモジュールが処理するため、スループットは手動で構築した場合と同じです。得られるのはクライアントのライフサイクル管理です。SSH で設定ファイルを編集せずに、peer の追加、無効化、削除を行えます。一方で、その設定を直接制御できなくなります。この点については、VPS での手動 WireGuard 構築で説明します。

必要なのは、パブリック IPv4 アドレスを持つ KVM VPS、Compose plugin を備えた Docker Engine、および root アクセスです。OpenVZ や LXC など、ホストのカーネルを共有するコンテナ仮想化環境では、通常 WireGuard モジュールをロードできません。そのため、コンテナはインターフェースを起動できずに失敗します。

Version 15 では設定が環境変数から移動しました

多くのガイドは wg-easy 14 向けに書かれています。このバージョンでは、WG_HOST にサーバーアドレスを設定し、PASSWORD_HASH に管理者パスワードの bcrypt ハッシュを設定します。どちらも環境変数です。Version 15 は全面的に書き直されています。公式の移行 notes には、v15 は v14 と同じ環境変数を使用せず、その大半が Web UI の管理パネルに移動したと明記されています。

そのため、WG_HOSTPASSWORD_HASH はもう何も行いません。古い compose ファイルをコピーすると、コンテナは起動しますが、それらの行を無視し、ブラウザーで管理者アカウントを作成するよう求めます。これはバグではありません。新しいセットアップ手順です。

2026 年 7 月時点で固定すべき major tag は 15 です。latest を使用せず、major version を固定してください。major upgrade ではディスク上の設定形式が変更され、正常にロールバックできないためです。

Compose ファイル

スタック用のディレクトリを作成し、公式の Compose ファイルをその中に書き込みます。これは upstream のファイルを変更せずに使用したものです。

sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
  https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml

内容は次のようになります。

volumes:
  etc_wireguard:

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    networks:
      wg:
        ipv4_address: 10.42.42.42
        ipv6_address: fdcc:ad94:bacf:61a3::2a
    volumes:
      - etc_wireguard:/etc/wireguard
      - /lib/modules:/lib/modules:ro
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1
      - net.ipv6.conf.default.forwarding=1

networks:
  wg:
    driver: bridge
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: 10.42.42.0/24
        - subnet: fdcc:ad94:bacf:61a3::/64

etc_wireguard は、サーバーキーと作成したすべてのクライアントを保持する名前付きボリュームです。このボリュームをバックアップしてください。バックアップしないと、再構築時にすべてのピアが失われます。これらのファイルをホストのファイルシステム上で確認したい場合は、bind mount に置き換えてください。その前に、bind mount と名前付きボリュームの違い を確認してください。権限の動作が異なるためです。

NET_ADMIN、SYS_MODULE、sysctl が必要な理由

デフォルトでは、コンテナはネットワークスタックを操作できません。これらの各行は、それぞれ特定の制限を解除します。

NET_ADMIN により、コンテナは wg0 インターフェースを作成し、アドレスを割り当て、ルートを書き込めます。これがないと、コンテナは起動した後、インターフェースの起動処理中に終了します。ip link add wg0 type wireguardOperation not permitted を返すためです。

SYS_MODULE と読み取り専用の /lib/modules マウントを組み合わせると、ホストが WireGuard カーネルモジュールをまだロードしていない場合でも、コンテナはそのモジュールをロードできます。モジュールはイメージ内ではなくホストのカーネルに存在するため、ホストのディレクトリをコンテナから見えるようにする必要があります。最新のカーネルでは、通常このモジュールは組み込み済みです。ホスト上で sudo modprobe wireguard && echo ok を実行すると確認できます。

net.ipv4.ip_forward=1 により、カーネルは自身宛てではないパケットを転送します。これがないと、クライアントは接続し、ハンドシェイクも成功しますが、インターネット宛てのすべてのパケットが破棄されます。そのため、VPN が接続済みに見えても ping 1.1.1.1 はタイムアウトします。

net.ipv4.conf.all.src_valid_mark=1 は、特に分かりにくい設定です。WireGuard は、自身が送信するパケットにマークを付け、そのパケットがトンネルへ再びルーティングされないようにします。厳格な reverse path filtering は、送信元アドレスが想定されたルートと一致しないパケットを検出すると、そのパケットを破棄します。この sysctl は、マーク付きパケットを受け入れるようカーネルに指示します。これにより、full tunnel が自身の通信によって機能しなくなるのを防ぎます。

起動して管理者アカウントを作成する

cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -f

docker compose updocker compose down を使用し、startstop は使用しないでください。Upstream は、異なる設定で作成されたコンテナに対して start を実行すると、ネットワークが不整合な状態になると警告しています。再起動後にスタックを復元する場合は、restart: unless-stopped ですでに対応できます。compose サービスの起動時の動作では、そのポリシーで保証されることと保証されないことを説明しています。

Web UI は TCP 51821 番ポートで待ち受けます。初回アクセス時にはセットアップ画面が表示され、管理者アカウントを作成し、クライアントがサーバーへ接続する際に使用するホストアドレスを確認します。このホストアドレスは、すべてのクライアント設定の Endpoint 行に入ります。そのため、VPS のパブリック IP または DNS 名を指定する必要があります。誤った値を指定すると、スマートフォンに渡した QR コードが到達不能な場所を指し、ハンドシェイクが完了しません。

このポートについて、もう1点注意が必要です。wg-easy 15 は、INSECURE=true を設定しない限り、平文の HTTP を拒否します。信頼されていない証明書を使用して HTTPS でアクセスする方法と、手前のリバースプロキシで TLS を終端する方法は、どちらも問題ありません。デフォルト設定のまま http:// でアクセスすることはできません。

UI ポートをインターネットに公開しない

compose ファイルは、すべてのインターフェースで 51821 を公開しています。これは通信をルーティングできるホストのログインページです。外部からアクセスできる状態にしてはいけません。Docker でポートを公開すると、DOCKER チェーンにルールが追加されます。このチェーンは ufw より前に評価されるため、ufw の deny ルールではポートを閉じられません。この落とし穴は単独で理解しておく価値があります。Docker の公開ポートが ufw を無視する理由で詳しく説明しています。

簡単な対処方法は、UI を loopback にバインドし、SSH トンネル経由でアクセスすることです。

    ports:
      - "51820:51820/udp"
      - "127.0.0.1:51821:51821/tcp"
    environment:
      - INSECURE=true

次に、ラップトップから次を実行します。

ssh -L 51821:127.0.0.1:51821 youruser@your.server.address

ラップトップのブラウザーで http://127.0.0.1:51821 を開きます。通信は SSH によって暗号化され、他のホストからポートに応答を受けることはありません。この場合、通常の HTTP 通信が loopback インターフェースから外に出ないため、INSECURE=true も安全です。

UDP 51820 番ポートを開放し、両方のファイアウォールを確認する

WireGuard では、インターネットから UDP 51820 番ポートに到達できる必要があります。Docker はこのポートを公開しますが、多くのプロバイダーでは、Docker が認識できない独立したネットワークファイアウォールを VPS の前段に配置しています。両方でポートを開放してください。ufw でホストのファイアウォールを管理している場合は、nftables を手動で記述するよりも VPS の基本的な ufw ルールを使う方が簡単です。

コンテナが実際に待ち受けていることを確認します。

sudo ss -ulnp | grep 51820

UDP ソケットが待ち受け状態になっていることを確認できるはずです。その行が表示されない場合、コンテナはインターフェースを起動できていません。sudo docker compose logs wg-easy に原因が表示されます。

クライアントを作成し、スマートフォンでスキャンする

UI でクライアントを作成し、後から識別できる名前を付けます。たとえば、所属するデバイス名を使います。wg-easy は次に利用可能なトンネルアドレスを割り当て、鍵ペアを生成します。各クライアント行には QR コードと、ダウンロード可能な .conf ファイルが表示されます。

スマートフォンに公式の WireGuard アプリをインストールし、QR コードからトンネルを追加する操作を選びます。画面に表示したコードへカメラを向けます。入力した名前でトンネルが表示されます。トンネルを有効にすると、UI のクライアント行に転送量のカウンターと直近のハンドシェイク時刻が表示され始めます。スマートフォンがトンネルに接続すると、インターネットへ公開していないサービスにもアクセスできます。そのため、サーバーで外部向けのポートを 1 つも開けずに、どこからでも 自宅運用の写真サーバーへスマートフォンからアップロードできます。同じ方法はメディアにも使えます。90 年代のビデオ店として再構築した Jellyfin ライブラリは、LAN 上と同じように非公開のまま、ホテルの部屋から快適に閲覧できます。同じトンネルではアラートを逆方向に送ることもできます。自宅運用の ntfy サーバーは、バックアップジョブが失敗した瞬間にスマートフォンへメッセージをプッシュできます。パブリックインターネットからのリクエストに応答する必要はありません。

有効化後もハンドシェイクが表示されないクライアントは、サーバーへまったく到達できていません。プロバイダーのファイアウォール、または設定に埋め込まれたエンドポイントアドレスにある UDP 51820 が原因です。ハンドシェイクは表示されるのにインターネットへ接続できないクライアントでは、フォワーディングまたは DNS を確認します。

デスクトップでは、.conf ファイルをダウンロードし、再入力せずに WireGuard クライアントへインポートします。このファイルの秘密鍵は 1 回だけ生成され、1 回だけ表示されます。SSH の秘密鍵と同じように、このファイルを慎重に扱ってください。

UIから移行すべきタイミング

wg-easyは、接続する相手が人やスマートフォンである間は適したツールです。設定ファイルを編集するよりUIの操作が速く、紛失したスマートフォンの接続を1回のクリックで無効化できます。

UIで扱えない構成が必要になると、限界に達します。サイト間ルーティングでは、通常ここが最初の壁になります。peerのAllowedIPsで単一のアドレスではなく、リモートサブネット全体を指定する構成です。peerごとのルーティングルールを使うスプリットトンネルや、プロビジョニングツールで生成した設定も次の段階です。その時点では、手書きの設定が難しいのではなく、方法が異なるだけです。WireGuardの基本ガイドでは、wg0.confから同じトンネルを構築する方法を説明しています。コントロールプレーン自体の運用をやめたい場合は、WireGuardとTailscaleの比較でマネージド構成を確認できます。適切なトレードオフかどうかは、調整サーバーから実際に到達できる範囲によって決まります。ネットワークを預ける前に、Tailscaleの信頼モデルも確認してください。次に問題になるのは通常コストです。Tailscaleの無料プランで実際に利用できる範囲であれば、家庭や小規模チームは料金を支払わずに利用できます。無料プランを超えると、課金単位はデバイスではなくユーザーになります。すでに料金を支払っているVPSとは異なる課金体系なので、チームを移行する前に無料プランを超えた場合のTailscaleの料金を確認してください。先ほど構築したフルトンネルには、Tailscaleにも直接対応する構成があります。VPSをTailscaleのexit nodeとして広告することで、各クライアントの設定に記述せず、管理コンソールで承認した同じ経路をサーバー経由で利用できます。サブネットにも対応する方法があります。VPSからプライベートネットワーク全体を広告すると、peerごとのAllowedIPs編集を行わずに、そのネットワークをtailnet内のすべてのデバイスへ提供できます。ダッシュボードと自動メッシュルーティングは必要だが、他者の調整サーバーは使いたくない場合は、VPSで独自のNetBirdサーバーを運用することで、所有するハードウェア上にコントロールプレーンを置けます。ただし、wg-easyでは不要だったDNSとTLSの設定が必要です。

上記のcompose構文がWireGuardではなく不慣れだった場合は、VPSでのDocker Composeの基本でファイル形式と日常的に使うコマンドを説明しています。

FAQ

wg-easy が WG_HOST と PASSWORD_HASH を無視するのはなぜですか?

これらの変数は wg-easy 14 用です。バージョン 15 は書き直されており、upstream はほぼすべての設定を Web UI の管理パネルへ移しました。コンテナはどちらの変数も読み取らないため、正常に起動した後、初回アクセス時に管理者アカウントの作成を求めます。セットアップページで、クライアントからアクセスするホストアドレスを設定してください。

カーネルに WireGuard が組み込まれている場合、SYS_MODULE は必要ですか?

いいえ。SYS_MODULE/lib/modules の mount は、ホストに WireGuard モジュールがない場合にコンテナがモジュールを読み込めるようにするためのものです。sudo modprobe wireguard がすでに成功するホストでは、この capability は使用されません。これを削除することは、適切な hardening です。どちらの場合も NET_ADMIN は引き続き必要です。

クライアントは接続しますが、インターネットにアクセスできません。何が問題ですか?

トラフィックがないハンドシェイクは、ほぼ常に forwarding が原因です。手作業で編集したコピーでは項目が失われやすいため、net.ipv4.ip_forward=1net.ipv4.conf.all.src_valid_mark=1 が compose file に残っていることを確認してください。forwarding が有効な場合は、クライアントが受け取った DNS server を確認します。すべてのトラフィックを VPN 経由で送信する一方、到達できなくなった DNS server を指定しているトンネルは、ブラウザー上では接続が停止した場合と同じように見えます。

クライアントをバックアップするにはどうすればよいですか?

すべてのデータは、etc_wireguard named volume 内の wg0.json file に保存されます。UI には同じデータをエクスポートする backup button もあります。アップグレード前に、その file をサーバー外の場所へコピーしてください。復元する場合は、新しいコンテナのセットアップ手順で upload します。

wg-easy を reverse proxy の背後で実行できますか?

はい。proxy を TCP 51821 の前段に配置して、そこで TLS を終端します。コンテナに INSECURE=true を設定し、proxy からの平文 HTTP の転送を受け入れられるようにしてください。VPN traffic は UDP であり HTTP proxy を通過しないため、UDP 51820 は直接 publish したままにします。