SSD Nodes Learn
ガイド Matt Connor著者 Matt Connor ・更新日 2026-07-24

VPSでWireGuard VPNを構築する方法と注意点

Linux VPSにWireGuardを導入する手順を解説します。keyの権限設定やIP forwarding、NATの設定に加え、OpenVZ環境での動作制限やhandshake失敗時のトラブルシューティングなど、構築時に陥りやすい具体的な問題点についても詳しく説明します。

作成するもの

自前のサーバーに WireGuard VPN を構築する場合、設定は約40行程度です。構成要素は、1つの key pair、1つの interface ファイル、1つの sysctl、1つの NAT rule、1つの firewall hole です。インストールは容易なため、本ガイドではトラブルシューティング(key の権限、AllowedIPs、forwarding、DNS)を中心に解説します。

WireGuard はカーネル内の Layer 3 tunnel です。Linux 5.6 からメインラインに採用されているため、Ubuntu 24.04 や Debian 13 では外部モジュールなしで動作します。暗号スイートのネゴシエーションや認証局(CA)、ユーザー名/パスワードによる認証は行いません。peer は、公開鍵と、その鍵が使用可能な IP address の組み合わせで定義されます。MAC チェックに失敗したパケットは、応答なしで破棄されます。そのため、ポートスキャンに対して応答しません。一方で、認証サーバーが存在しないため、アクセス権を削除するには、対象のサーバーから peer を削除する必要があります。

仮想化環境をまず確認してください

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 を適用するためです。権限(mode)を明示的に設定してください。

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 を使用すると、オプションで pre-shared key を追加できます。各 config ファイルに 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つのピアに同じ allowed IP を設定すると、最後に設定された方に移動します。最初に設定されていたピアには、エラーが表示されることなく通信が届かなくなります。SaveConfig を未設定のままにしてください。設定すると wg-quick down が実行時の状態からこのファイルを書き換えます。

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

Linux サーバーは、自分宛てではないパケットを破棄します。デフォルトでは、Forwarding と source 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 には egress インターフェースが必要です。つまり、wg0 ではなく、インターネットに接続する NIC を指定してください。eth0 と決めつけず、ip route show default で確認してください。現在のイメージでは、enp1s0ens3 といった名前が使用されているためです。

Firewall: the port, and the forward path

One nftables file covers filter and NAT. Write /etc/nftables.conf — it flushes the existing ruleset, so skip this on a box already managed by ufw or 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
  }
}

Apply it with sudo systemctl enable --now nftables, keeping a second SSH session open: policy drop plus a typo in the SSH rule locks you out of your own server. Note what the forward chain does not allow — wg0 to wg0. Peers reach the internet, not each other; add iifname "wg0" oifname "wg0" accept for a peer-to-peer VPN. The same chain governs what a peer may touch on the server itself, which matters when the box doubles as a remote development box running Claude Code in tmux and you would rather not expose that side of it publicly.

On a ufw box: ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" in /etc/default/ufw, and a *nat POSTROUTING MASQUERADE rule at the top of /etc/ufw/before.rules.

systemd で起動する

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

wg-quick は、インターフェースの作成、アドレスの追加、および AllowedIPs から派生したルートのインストールを行います。enable --now が重要な理由として、手動で行った wg-quick up wg0 は再起動後に消失するためです。また、kernel のアップグレード時にも再起動が必要になります。

クライアント設定と、多くの人が誤解している設定

[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つの異なる役割を同時に果たします。これらを混同することが、WireGuardに関する混乱の主な原因です。

アウトバウンド(送信)においては、ルーティングテーブルです。 送信先がピアの AllowedIPs と一致するパケットは、暗号化されてそのピアへ送信されます。0.0.0.0/0, ::/0 はすべての通信をトンネル経由にします。これは、サーバーをデフォルトルートとするフルトンネルです。スプリットトンネルは、より限定的なリストです。AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 はVPNトラフィックとサーバー背後のプライベートネットワークのみを転送し、それ以外の通信はローカルルートを維持します。この限定的なリストにより、サービスをパブリックインターネットから完全に分離できます。例えば、トンネルアドレスに紐付けられた VPS上のプライベートなNextcloudインスタンス や、同じマシン上で動作する nested-virtualisationラボのVM は、ピアからは到達可能ですが、他の誰からも見えません。

インバウンド(受信)においては、アクセス制御リスト(ACL)です。 送信元アドレスが、そのピアの AllowedIPs に含まれていないピアからの復号済みパケットは破棄されます。サーバーがノートPCに対して 10.8.0.2/32 をリストに含めるのはこのためです。そこに 0.0.0.0/0 と記述すると、そのクライアントがトンネル内の任意のIPアドレスを偽装できてしまいます。

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

DNS、そして誰も気づかないリーク

AllowedIPs = 0.0.0.0/0を使用し、DNS =行がない場合、クライアントはローカルネットワークから取得したリゾルバ(192.168.1.1にあるカフェのルーター)を保持します。そのルートはデフォルトルートよりも優先度が高いため、他の通信がトンネル内を通っている間も、DNSクエリはローカルリンクを介してクリアテキストで送信されます。通信内容はプライベートですが、ドメイン名のリストは公開された状態になります。

解決策は2つあります。1つは、DNSをパブリックリゾルバ(DNS = 9.9.9.9)に向けることです。この場合、クエリはトンネルを通ってサーバーから送信されますが、リゾルバ側には内容が伝わります。もう1つは、10.8.0.1にバインドしたunboundまたはdnsmasqを実行し、DNS = 10.8.0.1を設定して、input chainにudp dport 53 iifname "wg0" acceptを追加することです。この設定を行うと、リゾルバを意識する必要はなくなりますが、名前解決自体ができなくなります。

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

接続を維持したまま peer を追加・削除する方法

インターフェースを再起動してユーザーを追加すると、すべての接続が切断されます。wg0.conf[Peer] のブロックを追記し、その場で peer set をリロードしてください。

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

wg-quick strip は、wg-quick-only キー (Address, DNS, PostUp) を除いた設定を表示します。syncconf を使用すると、ライブセッションを維持したまま差分を適用できます。このコマンドは peer の更新のみを行います。Address が変更された場合は、依然として full down/up が必要です。sudo wg set wg0 peer <public key> remove で権限を剥奪した後、ファイルからブロックを削除してください。削除しない場合、次回のリロード時に設定が復元されます。

失敗パターンと表示される文字列

Handshakeが完了しない。 wg showlatest handshake がないピアが表示され、クライアントのログには次のように表示されます:

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

パケットが届いていないか、あるいは受理されていません。以下の点を確認してください。VPSのファイアウォールと、プロバイダーのネットワークファイアウォール(多くの管理パネルで個別に設定が必要)の両方で UDP 51820 が開放されているか。Endpoint のアドレスとポートが正しいか。鍵が正しく設定されているか。クライアントの [Peer] ブロックにある鍵は、サーバーの公開鍵である必要があります(逆も同様)。秘密鍵やクライアント自身の公開鍵を貼り付けると、この症状が発生します。サーバー側の sudo tcpdump -ni any udp port 51820 で、パケットが届いているかを確認できます。デフォルトではカーネルモジュールにログは出力されません。WireGuardのメッセージは、dynamic debug (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control) を有効にした場合のみ dmesg に表示されます。有効な状態で鍵が一致しない場合、invalid-MAC drop として表示されます。

Handshakeは成功するが、インターネットに接続できない。 ping 10.8.0.1 は成功しますが、ping 1.1.1.1 でタイムアウトします。これは、forwarding または NAT の設定が不足していることが原因です。sysctl net.ipv4.ip_forward1 を読み取っているか確認し、sudo nft list ruleset または sudo iptables -t nat -L POSTROUTING -n -v を使用してクライアントから ping を送信しながらカウンターを監視してください。masquerade ルールのパケットカウンターが 0 の場合は、egress インターフェース名が間違っています。カウンターが増加するが返信がない場合は、forward chain のポリシーに問題があります。

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

一部の HTTPS サイトが停止する。 SSH や ping は正常ですが、サイズの大きいページが止まります。これは path MTU の問題です。トンネルによるオーバーヘッドにより、経路上のデバイスが ICMP メッセージを返さずに、サイズ超過のパケットを破棄しています。クライアントの [Interface]MTU を下げてください。1420、次に 1380、最後に 1280 を試してください。

インターフェースが起動しない。 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、有効期限がないため、クライアントごとに新しい key pair が生成されます。

並行して移行してください。同じサーバー上で、UDP 51820 の WireGuard と 1194 の OpenVPN を同時に運用できます。まず wg0 を構築し、クライアントを一つずつ移行してから、古いサービスを停止してください。OpenVPN の username/password および失効モデル(revocation model)は引き継げません。アカウント管理や監査ログが必要な場合は、WireGuard の上位レイヤーで実装してください。

バックアップ、アップグレード、および大規模運用時の負荷

/etc/wireguard がサーバーです。バックアップ(sudo tar czf wg-backup.tgz -C /etc wireguard、mode 600、外部ストレージに保存)を取っておけば、新しい VPS 上に数分で再構築できます。サーバーの private key を紛失すると、クライアントはサーバーの public key を固定(pin)しているため、すべてのクライアント設定を再発行する必要があります。アップグレードは通常の apt upgrade で済み、kernel の更新には再起動が必要です。wg-quick@wg0 を有効にしていれば、自動的に復旧します。

ピアごとのステートは小さく、暗号化処理は kernel 内で行われます。そのため、ボトルネックは本設定ではなく、VPS の CPU と帯域幅の制限になります。公開されている数値ではなく、iperf3 を使用して tunnel 越しに測定してください。大規模運用で負荷がかかるのは運用面です。各ピアには一意の tunnel IP が必要です。60 個もの [Peer] ブロックを手動で編集すると、AllowedIPs の重複が発生しやすくなります。設定はスクリプトで生成してください。サーバーが1つであることは、UDP endpoint が1つであり、単一障害点(single point of failure)であることを意味します。WireGuard にはクラスタリング機能はありません。冗長化するには、独自の key を持つ2台目のサーバーを用意してください。key のローテーションは手動で行う必要があるため、誰がどの key を保持しているか、およびどのように key を失効させるかを記録しておいてください。

これらすべてを実行するには、管理権限のある Linux マシンが必要です。パブリック IP、モジュールをロード可能な kernel、およびエンドツーエンドで制御可能な firewall が必要です。

FAQ

なぜ WireGuard の handshake が完了しないのですか?

wg showlatest handshake が設定されていない peer が表示される場合、パケットが届いていないか、拒否されています。VPS の firewall とプロバイダーのネットワーク firewall の両方で UDP 51820 を確認してください。Endpoint の host と port が正しいことを確認し、鍵の設定ミスがないか確認してください。クライアントの [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) を有効にした後にのみ handshake の失敗を報告します。鍵の不一致は invalid-MAC drop として表示されます。

接続は確立されますが、インターネットにアクセスできません。何が不足していますか?

ping 10.8.0.1 は動作しているが ping 1.1.1.1 がタイムアウトする場合、forwarding または NAT に問題があります。sysctl net.ipv4.ip_forward1 が読み取れること、および /etc/sysctl.d/ で設定されていることを確認してください。sysctl -w のような再起動で消える設定では不十分です。次に、ip route show default から実際の egress interface を特定し、masquerade rule を確認してください。通常は enp1s0 または ens3 で、eth0 となることは稀です。

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

Full tunnel 設定で DNS = 行がない場合、クライアントはローカルネットワークから取得した resolver を使用し続けます。その結果、他の通信は tunnel を通りますが、DNS クエリはローカルリンクを cleartext で流れます。DNS を public resolver に設定するか、unbound/dnsmasq10.8.0.1 にバインドして、input chain で udp dport 53 iifname "wg0" を許可してください。

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

これには 2 つの役割があります。送信(Outbound)においては routing table として機能します。peer の AllowedIPs に一致するトラフィックは暗号化され、その peer へ送信されます。受信(Inbound)においては access-control list として機能します。送信元がその peer の AllowedIPs の外側である復号済みパケットは破棄されます。サーバー側ではクライアントごとに /32 が必要ですが、クライアント側では 0.0.0.0/0 だけで済む理由がこれです。

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

KVM VPS では、in-kernel module を使用して追加設定なしで動作します。OpenVZ や LXC のようなホストの kernel を共有するコンテナ型仮想化では、modprobe wireguardOperation not supported で失敗するため、fallback として wireguard-go の userspace 実装を使用します。まず最初に sudo modprobe wireguard && echo ok を実行してください。

#wireguard#vpn#linux-networking#nftables#systemd#self-hosting