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

WireGuard VPNをVPSに自分で構築する方法

Linux VPSでWireGuardを構築します。鍵生成、wg0.conf、IP転送、NAT、AllowedIPs、DNSに加え、KVMとLXCで異なる失敗原因やハンドシェイク不良も解説します。

構築するもの

自分で管理するサーバー上に WireGuard VPN を構築する場合、設定は約40行です。必要なのは、1組の鍵ペア、1つのインターフェースファイル、1つの sysctl 設定、1つの NAT ルール、1つのファイアウォールの許可です。インストールは簡単なので、このガイドでは主に、問題が発生する箇所、鍵の権限、AllowedIPs、転送、DNS について説明します。

WireGuard はカーネル内で動作する Layer 3 トンネルで、Linux 5.6 以降は mainline に含まれています。そのため、Ubuntu 24.04 と Debian 13 では外部モジュールなしで利用できます。暗号方式のネゴシエーション、認証局、ユーザー名とパスワードによる認証はありません。ピアは公開鍵と、その鍵で使用できる IP アドレスの組み合わせです。MAC の検証に失敗したパケットは応答なしで破棄されるため、ポートはスキャンに応答しません。一方、認証サーバーは存在しないため、アクセスを削除するにはサーバー上でピアを削除します。

仮想化方式を最初に確認する

WireGuard にはモジュールをロードできる kernel が必要です。KVM VPS では、通常そのまま動作します。ホストの kernel を共有するコンテナ型仮想化、OpenVZ、LXC では、最初のコマンドが RTNETLINK answers: Operation not supported で失敗します。この場合の代替手段は、wireguard-go userspace 実装です。最初に sudo modprobe wireguard && echo ok で確認します。

秘密鍵を漏えいさせずに生成する

誰でも読み取れる /etc/wireguard/server.key は、VPN がない状態と同じです。一般的な umask 077 && wg genkey | sudo tee ... 行は信頼できません。sudotee の作成時に独自の umask を適用するためです。モードを明示的に設定します。

sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.key

クライアント鍵ペアも同じ方法で生成します。wg genpsk はオプションの事前共有鍵を追加します。各設定ファイルに 1 行ずつ記述します。

サーバーのインターフェース: /etc/wireguard/wg0.conf

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>

[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32

chmod 600 です。ファイルが全ユーザーからアクセス可能であるという起動時の警告が表示される場合は、この設定を省略しています。Address はトンネル内でのサーバーのアドレスで、VPN サブネット全体のマスクを含みます。実際のネットワークで使われない範囲を選んでください。192.168.1.0/24 はクライアントが接続する家庭用ルーターの半数と競合し、その場合、トンネルの経路はローカルルートに負けて通信が黙って失敗します。

サーバー側でピアの AllowedIPs/32 です。これは、そのクライアントが所有するトンネルアドレスです。2 つのピアに同じ許可 IP を指定すると、最後に設定したピアへ移動します。先に設定したピアは、どこにもエラーを出さずに通信を受信できなくなります。SaveConfig は未設定のままにしてください。設定すると、wg-quick down が稼働中の状態に基づいてこのファイルを書き換えます。

ボックスをルーターにする

Linux サーバーは、自身宛てでないパケットを破棄します。転送と送信元 NAT は、デフォルトではどちらも無効です。

printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
  | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forward

基本的な sysctl -w は、次回の再起動までは動作しますが、その後は何も表示せず停止します。NAT には、インターネットへ接続する NIC である egress インターフェースが必要です。wg0 ではありません。eth0 だと決めつけず、ip route show default から自分の環境の値を取得してください。現在のイメージでは、enp1s0ens3 のような名前が使われます。

ファイアウォール: ポートと転送経路

フィルターと NAT は 1 つの nftables ファイルで設定します。/etc/nftables.conf を実行すると、既存のルールセットが flush されます。そのため、すでに ufw または Docker で管理しているホストでは実行しないでください。

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    iif lo accept
    tcp dport 22 accept
    udp dport 51820 accept
  }
  chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related accept
    iifname "wg0" oifname "enp1s0" accept
  }
}

table ip nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
  }
}

2 つ目の SSH セッションを開いたまま、sudo systemctl enable --now nftables で適用します。policy drop に加えて SSH ルールをタイプミスすると、自分のサーバーから締め出されます。forward chain が許可していない内容、つまり wg0 から wg0 への通信に注意してください。ピアはインターネットには接続できますが、互いには接続できません。ピア間 VPN が必要な場合は iifname "wg0" oifname "wg0" accept を追加します。同じ chain は、ピアがサーバー自体でアクセスできる対象も制御します。サーバーを tmux で Claude Code を実行するリモート開発環境 としても使う場合、この点は重要です。その環境側を外部に公開したくないことがあるためです。

ufw を使用するホストでは、ufw allow 51820/udp/etc/default/ufw 内の DEFAULT_FORWARD_POLICY="ACCEPT"、および /etc/ufw/before.rules の先頭に POSTROUTING MASQUERADE ルール *nat を設定します。

systemd で起動する

sudo systemctl enable --now wg-quick@wg0
sudo wg show

wg-quick はインターフェースを作成し、アドレスを追加し、AllowedIPs から導出したルートを設定します。重要なのは enable --now です。手動で実行した wg-quick up wg0 は、次回の再起動後には失われます。カーネルのアップグレードでも再起動が発生します。再起動後に起動し直せない unit は、誰かが接続を試みるまで何も知らせません。そのため、wg-quick@wg0OnFailure= の drop-in を設定し、自分で用意した ntfy サーバーへ通知する方法が最も低コストです。接続できなくなったユーザーから知らされるのではなく、スマートフォンで異常を把握できます。

クライアント設定と、誰もが間違える設定

[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1

[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

AllowedIPs には 2 つの異なる役割があり、この 2 つを混同することが WireGuard に関する混乱の主な原因です。

送信方向では、ルーティングテーブルです。 宛先がピアの AllowedIPs に一致するパケットは暗号化され、そのピアへ送信されます。0.0.0.0/0, ::/0 はすべての通信をトンネル経由にします。これはフルトンネルで、サーバーをデフォルトルートにする構成です。スプリットトンネルでは、より限定したリストを指定します。AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 は VPN トラフィックと、サーバーの背後にある 1 つのプライベートネットワークを通し、それ以外の通信はローカルルートを使用します。この限定したリストにより、サービスをパブリックインターネットから完全に切り離せます。たとえば、トンネルアドレスにバインドした VPS 上のプライベートな Nextcloud インスタンスや、同じホスト上で動作する ネスト型仮想化のラボ VMを、ピアからは到達可能にし、それ以外の利用者からは見えない状態にできます。

受信方向では、アクセス制御リストです。 復号したパケットの送信元アドレスが、そのピアの AllowedIPs に含まれていない場合、そのパケットは破棄されます。そのため、サーバーではノート PC に対して 10.8.0.2/32 を指定します。そこに 0.0.0.0/0 を指定すると、そのクライアントはトンネル内の任意のアドレスを偽装できてしまいます。

PersistentKeepalive は、NAT 配下のクライアントで使用します。ルーターは、パケットが流れている間だけ UDP マッピングを維持するためです。マッピングの有効期限が切れると、サーバーはクライアントに到達できなくなります。PersistentKeepalive = 25 はマッピングを維持します。パブリック IP を持つサーバーではなく、クライアント側に設定してください。

DNS と、誰も気付かない漏えい

AllowedIPs = 0.0.0.0/0 を使用し、DNS = の行が ない 場合、クライアントはローカルネットワークから取得したリゾルバー、つまり 192.168.1.1 のカフェのルーターを使い続けます。この経路はデフォルト経路より具体的なため、DNS クエリは暗号化されていない状態でローカルリンクから送信され、それ以外の通信だけがトンネル経由になります。通信の内容は非公開でも、名前の一覧は非公開になりません。

正直な選択肢は2つあります。DNS をパブリックリゾルバー (DNS = 9.9.9.9) に向けると、クエリはトンネルを通ってサーバーから外部へ出ます。ただし、そのリゾルバーからはクエリを確認できます。もう1つは、unbound または dnsmasq10.8.0.1 にバインドして実行し、DNS = 10.8.0.1 を設定し、入力チェーンに udp dport 53 iifname "wg0" accept を追加する方法です。その行を設定してリゾルバーを無視すると、名前解決は一切行われません。

Linux クライアントでは wg-quickDNSresolvconf に適用します。これがない場合は resolvconf: command not found になります。openresolv をインストールするか、systemd-resolved クライアントで PostUp = resolvectl dns %i 10.8.0.1 を設定してください。

トンネルを停止せずにピアを追加・削除する

ユーザーを追加するためにインターフェースを再起動すると、接続中の全員が切断されます。[Peer]ブロックをwg0.confに追記し、その後、ピア一覧を稼働中のまま再読み込みします。

sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'

wg-quick stripは、wg-quick専用のキー(AddressDNSPostUp)を除いた設定を出力し、syncconfは稼働中のセッションを維持したまま差分を適用します。更新されるのはピアだけです。Addressを変更した場合は、完全な停止・起動が必要です。sudo wg set wg0 peer <public key> removeで失効させた後、ファイルからブロックを削除してください。削除しないと、次回の再読み込みで再び追加されます。

障害時に表示されるメッセージ

ハンドシェイクが完了しません。 wg show にピアが表示されますが、latest handshake がありません。クライアントのログは次のようになります。

Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)

パケットが到着していないか、受け入れられていません。次の順に確認します。UDP 51820 が VPS のファイアウォールで許可されているか。多くのコントロールパネルでは別に設定する、プロバイダー側のネットワークファイアウォールでも許可されているか。Endpoint のアドレスとポートが正しいか。鍵を取り違えていないか。クライアントの [Peer] ブロックにはサーバーの公開鍵を指定し、その逆も同様にします。秘密鍵やクライアント自身の公開鍵を貼り付けると、まったく同じ症状になります。サーバー上の sudo tcpdump -ni any udp port 51820 で、パケットが到着しているかどうかを確認できます。カーネルモジュールはデフォルトではログを出力しません。WireGuard のメッセージは、動的デバッグを有効にした後の dmesg にのみ表示されます。動的デバッグを有効にすると、鍵の不一致は invalid-MAC による破棄として表示されます(echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control)。

ハンドシェイクは成功しますが、インターネットに接続できません。 ping 10.8.0.1 は成功しますが、ping 1.1.1.1 はタイムアウトします。フォワーディングまたは NAT が不足しています。sysctl net.ipv4.ip_forward1 を示していることを確認し、クライアントから ping を実行しながら、sudo nft list ruleset または sudo iptables -t nat -L POSTROUTING -n -v でカウンターを監視します。masquerade ルールのパケット数が 0 の場合、送信インターフェース名が間違っています。カウンターが増えるのに応答がない場合は、forward chain のポリシーを確認します。

インターネットには接続できますが、名前を解決できません。 ping 1.1.1.1 は成功し、curl https://example.comCould not resolve host を返します。DNS の行がないか、トンネル内から到達できないリゾルバーを指定しています。

一部の HTTPS サイトで接続が停止します。 SSH と ping は正常ですが、大きなページの読み込みが停止します。これはパス MTU の問題です。トンネルによってオーバーヘッドが追加されるため、途中のリンクでサイズ超過パケットが破棄され、ICMP メッセージが返らないことがあります。クライアントの [Interface]MTU を下げ、1420、次に 1380、その後 1280 を試します。MTU を下げると停止は解消するもののスループットが改善しない場合は、切りのよい数値を推測し続けないでください。二分探索で実際のパス MTU を特定し、TCP MSS を調整する手順を進めると、トンネル以外の原因も切り分けられます。

インターフェースが起動しません。 Address already in use は、別のプロセスが UDP 51820 を使用していることを示します。失敗した up の後に Cannot find device wg0 が表示される場合は、設定が拒否されています。journalctl -u wg-quick@wg0 -n 50 を確認してください。

Streisand または OpenVPN からの移行

Streisand は保守されておらず、リポジトリもアーカイブされています。廃止された自動化で VPN を運用し続けると、セキュリティ上の問題が徐々に蓄積します。インプレースアップグレードはできません。また、OpenVPN の PKI を変換することもできません。WireGuard には証明書、CA、有効期限がないため、各クライアントに新しい鍵ペアを発行します。

並行して移行します。同じサーバー上で、WireGuard は UDP 51820、OpenVPN は 1194 を使用して共存できます。wg0 を構築し、クライアントを 1 台ずつ移行してから、古いサービスを停止します。OpenVPN のユーザー名・パスワードと失効モデルは引き継げません。アカウント管理や監査証跡が必要な場合は、WireGuard の上位層にその機能を追加します。

大規模運用で負荷になるバックアップ、アップグレード、要素

/etc/wireguard サーバーそのものです。バックアップを取得し(sudo tar czf wg-backup.tgz -C /etc wireguard、mode 600、サーバー外に保管)、新しい VPS 上に数分で再構築できるようにします。サーバーの秘密鍵を失うと、クライアントがサーバーの公開鍵を固定しているため、すべてのクライアント設定を再発行する必要があります。アップグレードは通常の apt upgrade と、kernel 更新時の再起動だけで済みます。有効化していれば、wg-quick@wg0 は自動的に復帰します。

peer ごとの状態は小さく、暗号処理は kernel 内で実行されます。そのため、上限を決めるのはこの設定ではなく、VPS に割り当てられた CPU と帯域幅です。公開されている数値をそのまま信用せず、トンネル全体で iperf3 を使って測定します。大規模化で負荷になるのは運用です。すべての peer に一意のトンネル IP が必要です。60 個の [Peer] ブロックを手作業で編集すると、重複した AllowedIPs が入り込むため、スクリプトから設定を生成します。1 台のサーバーは 1 つの UDP エンドポイントであり、単一障害点でもあります。WireGuard にはクラスタリング機能がないため、冗長化には独自の鍵を持つ 2 台目のサーバーが必要です。鍵のローテーションも手動のままなので、どの鍵を誰が保持しているか、また 1 つの鍵を無効化する方法を記録しておきます。この管理情報がテキストファイルでは扱いきれなくなったら、一般的には同じ kernel データプレーン上にコントロールプレーンを追加します。self-hosted NetBird サーバーを使えば、アドレス割り当て、peer への配布、セットアップキーの管理を手作業で行わずに済みます。コントロールプレーンを自分で運用すること自体がサーバーを 1 台増やしすぎる場合は、Tailscale が代わりにホストします。無料プランでは 6 ユーザーが無制限のデバイスを利用できるため、個人で運用する環境の多くでは料金が発生しません。それを超えると、料金はマシン数ではなく人の数に応じて増えます。つまり、家庭や小規模チームが実際に支払う金額は、wg0.conf に手作業で追加する peer 数ではなく、ログインを持つユーザー数で決まります。この構成では、split-tunnel の AllowedIPs 管理を、subnet router からプライベート範囲を広告する構成に置き換えられます。1 台の VPS から 1 回だけ通知し、すべてのクライアントファイルに貼り付ける代わりに、中央で承認します。この選択が適切かどうかは、ホスト型コントロールプレーンが実際にどこまで到達できるかで決まります。また、通信を暗号化する鍵を保持することはありませんが、どの peer 同士が互いの情報を学習するかは決定します

これらすべてには、管理下にある Linux ボックス、パブリック IP、モジュールをロードできる kernel、そしてエンドツーエンドで管理できる firewall が必要です。

FAQ

WireGuard のハンドシェイクが完了しないのはなぜですか?

wg showlatest handshake のない peer が表示される場合、パケットが到達していないか、受け入れられていません。VPS の firewall と、プロバイダーが別途提供する network firewall の両方で UDP 51820 を確認します。Endpoint の host と port が正しいことを確認し、次に key が入れ違っていないか確認します。クライアントの [Peer] ブロックには、サーバーの public key を指定する必要があります。サーバー上の sudo tcpdump -ni any udp port 51820 で、パケットがそもそも到達しているか確認できます。dmesg は、dynamic debug (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control) を有効にした後に WireGuard のハンドシェイク失敗だけを報告します。その場合、key の不一致は invalid-MAC drop として現れます。

トンネルは接続しますが、インターネットに接続できません。何が不足していますか?

ping 10.8.0.1 が動作している一方で ping 1.1.1.1 が timeout になる場合、forwarding または NAT に問題があります。sysctl net.ipv4.ip_forward1 になっていることを確認し、それが再起動時に消える sysctl -w だけでなく、/etc/sysctl.d/ に設定されていることを確認します。次に、masquerade rule が ip route show defaultenp1s0、またはまれに ens3 から実際の egress interface を指定しているか確認します。eth0

クライアント設定に DNS = 行は必要ですか?

full tunnel で DNS = 行がない場合、クライアントは local network から取得した resolver を使い続けます。そのため、それ以外の通信がトンネルを通る一方で、これらの query は local link 上を平文で送信されます。DNS を public resolver に設定するか、unbound/dnsmasq10.8.0.1 に bind して実行し、input chain で udp dport 53 iifname "wg0" を開放します。

AllowedIPs は実際に何を制御しますか?

2 つの役割があります。送信時には routing table として機能し、peer の AllowedIPs に一致する traffic を暗号化して、その peer に送信します。受信時には access-control list として機能し、復号後の packet の source がその peer の AllowedIPs 外にある場合、その packet を破棄します。そのため、サーバー側では各クライアントに /32 を 1 つ指定し、クライアント側では 0.0.0.0/0 を指定する場合があります。

WireGuard はどの VPS でも動作しますか?

KVM VPS では、kernel 内の module により追加設定なしで動作します。OpenVZ や LXC など、host kernel を共有する container virtualisation では、modprobe wireguardOperation not supported で失敗します。その場合の代替手段は wireguard-go userspace implementation です。何よりも先に sudo modprobe wireguard && echo ok を実行します。