SSD Nodes Learn メモリ 8GB — 年額 $66
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-01

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

Docker Composeでwg-easyを構築する手順です。WireGuardのポート、NET_ADMIN、必要なsysctl、Version 15で環境変数から移行した設定、スマートフォンのQRコード接続を解説します。

構築するもの

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

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

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

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

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

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

2026年7月時点で固定すべきメジャータグは 15 です。latest は使用せず、メジャーバージョンを固定してください。メジャーアップグレードではディスク上の設定形式が変更され、正常にロールバックできないためです。

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 が必要な理由

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

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

SYS_MODULE と読み取り専用の /lib/modules マウントにより、ホストで WireGuard がまだロードされていない場合でも、コンテナは 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 は、自身が送信するパケットにマークを付け、それらがトンネルへ戻されないようにします。厳格な逆引きパスフィルタリングは、送信元アドレスが想定されるルートと一致しないパケットを検出して破棄します。この sysctl は、マーク付きパケットを受け入れるようカーネルに指示します。これにより、フルトンネルが自身の通信を遮断することを防ぎます。

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

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

startstop ではなく、docker compose updocker compose down を使用します。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ルールを使う方が簡単です。

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

sudo ss -ulnp | grep 51820

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

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

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

スマートフォンに公式のWireGuardアプリをインストールし、QRコードからトンネルを追加するように選択します。画面に表示されたコードにカメラを向けます。トンネルには入力した名前が表示されます。トンネルを有効にすると、UIのクライアント行に転送カウンターと直近のハンドシェイク時刻が表示されます。

有効にした後もハンドシェイクが表示されないクライアントは、サーバーにまったく到達していません。プロバイダーのファイアウォールまたは設定に組み込まれたエンドポイントアドレスで、UDP 51820が問題になっている可能性があります。ハンドシェイクは表示されるもののインターネットを利用できないクライアントでは、フォワーディングまたはDNSに問題があります。

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

UIから移行するタイミング

wg-easyは、接続先が人やスマートフォンである間は適したツールです。設定ファイルを編集するよりUIのほうが速く、紛失したスマートフォンのアクセスを1回のクリックで取り消せます。

UIでモデル化されていない構成が必要になると、制限に達します。通常、最初に問題になるのはサイト間ルーティングです。これは、ピアのAllowedIPsが単一のアドレスではなく、リモートサブネット全体を対象にする構成です。ピアごとのルーティングルールを使用するスプリットトンネルや、プロビジョニングツールで生成した設定も、次の段階で必要になります。その時点では、手動で記述する設定が難しいのではなく、方法が異なるだけです。通常のWireGuardガイドでは、wg0.confから同じトンネルを構築する方法を説明しています。コントロールプレーン自体の運用をやめたい場合は、WireGuardとTailscaleの比較でマネージドな選択肢を確認できます。

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

FAQ

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

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

カーネルに WireGuard がすでにある場合、SYS_MODULE は必要ですか?

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

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

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

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

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

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

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