Homelab WireGuard VPN
WireGuardは高速でモダンなVPNプロトコルです。自宅ネットワークへのリモートアクセスに最適な選択肢であり、OpenVPNよりも設定が簡単で、ほとんどの代替手段よりも高速です。
すべての設定例は一般的なセットアップを示しています。各コマンド(特にiptables転送ルールと鍵ファイルのパーミッション)をシステムに適用する前に確認し、メンテナンスウィンドウ内で変更を行ってください。
使用する場面
- Raspberry Pi、Linuxホスト、pfSense、またはルーター上でWireGuardサーバーをセットアップする場合
- WireGuardの鍵ペアを生成し、ピア設定ファイルを作成する場合
- スマートフォンやラップトップから自宅ネットワークへのリモートアクセスを設定する場合
- スプリットトンネリング(自宅トラフィックのみルーティング)とフルトンネル(全トラフィックをルーティング)の違いを説明する場合
- 接続が確立しないWireGuard接続のトラブルシューティングを行う場合
- 複数のクライアント向けにピア設定生成を自動化する場合
WireGuardの仕組み
あなたのスマートフォン(WireGuardクライアント)
│
│ 暗号化されたUDPトンネル(ポート51820)
│
あなたのホームルーター(WireGuardサーバー — パブリックIPまたはDDNSが必要)
│
あなたの自宅ネットワーク(192.168.1.0/24、NAS、Piなど)
すべてのデバイスは鍵ペア(公開鍵 + 秘密鍵)を持ちます。
サーバーは各クライアントの公開鍵を保持します。
クライアントはサーバーの公開鍵 + エンドポイント(IP:ポート)を保持します。
トラフィックはエンドツーエンドで暗号化され、中央サーバーや認証局は不要です。
サーバー設定(Linux)
# WireGuardのインストール
sudo apt update && sudo apt install wireguard -y
# サーバーの鍵ペアを生成 — 最初からプライベートパーミッションでファイルを作成
sudo mkdir -p /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server_private.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key'
# サーバー設定を記述 — 実際の秘密鍵の値に置き換えてください
# 秘密鍵をバージョン管理システムや共有してはなりません
sudo tee /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.8.0.1/24 # VPNサブネット — サーバーは .1
ListenPort = 51820
PrivateKey = <paste_server_private_key_here>
# スコープ指定転送ルール:VPNトラフィックのみ許可、汎用的なFORWARD ACCEPTは行わない
PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# スマートフォン — 実際のスマートフォンの公開鍵に置き換えてください
PublicKey = <phone_public_key>
AllowedIPs = 10.8.0.2/32
[Peer]
# ラップトップ — 実際のラップトップの公開鍵に置き換えてください
PublicKey = <laptop_public_key>
AllowedIPs = 10.8.0.3/32
EOF
sudo chmod 600 /etc/wireguard/wg0.conf
# eth0を実際のアウトバウンドインターフェース名に置き換えてください
# 確認コマンド:ip route show default
# IP転送を有効化(サーバー経由でのトラフィックルーティングに必要)
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
# WireGuardを起動し、起動時に自動起動するよう設定
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
クライアント設定
# クライアントデバイスごとに一意の鍵ペアを生成
# クライアントで実行するか、サーバーで秘密鍵を安全に転送 — 平文では不可
umask 077
wg genkey | tee phone_private.key | wg pubkey > phone_public.key
# クライアント設定ファイル(phone_wg0.conf):
[Interface]
PrivateKey = <phone_private_key>
Address = 10.8.0.2/32
DNS = 192.168.1.2 # オプション:トンネル経由でPi-holeをDNSとして使用
[Peer]
PublicKey = <server_public_key>
Endpoint = your-home-ip.ddns.net:51820 # パブリックIPまたはDDNSホスト名
AllowedIPs = 192.168.1.0/24 # スプリットトンネル:自宅ネットワークトラフィックのみ
# AllowedIPs = 0.0.0.0/0, ::/0 # フルトンネル:すべてのトラフィックをVPN経由
PersistentKeepalive = 25 # NATホールを維持(モバイルクライアントに必須)
スプリットトンネル vs フルトンネル
# スプリットトンネル:AllowedIPs = 192.168.1.0/24
VPN経由で送信されるのは自宅ネットワーク宛てのトラフィックのみ。
インターネットトラフィック(YouTube、Spotify)は直接送信 — モバイルでのパフォーマンスが向上。
最適な用途:「NASとPiにどこからでもアクセスしたいだけ」
# フルトンネル:AllowedIPs = 0.0.0.0/0, ::/0
すべてのトラフィックが自宅のインターネット接続を経由。
用途:自宅のDNS/Pi-hole広告ブロックの利用。
欠点:自宅のアップロード速度がどこでもボトルネックになる。
# マルチサブネットスプリットトンネル(最も一般的なホームラボのユースケース):
AllowedIPs = 192.168.10.0/24, 192.168.20.0/24, 192.168.30.0/24, 10.8.0.0/24
すべてのVLANをトンネル経由でルーティング。インターネットは直接接続を維持。
鍵生成とピア管理
import subprocess
def generate_keypair() -> tuple[str, str]:
"""WireGuard鍵ペアを生成します。戻り値は (秘密鍵, 公開鍵)。"""
private = subprocess.check_output(["wg", "genkey"]).decode().strip()
public = subprocess.run(
["wg", "pubkey"], input=private.encode(), capture_output=True
).stdout.decode().strip()
return private, public
def generate_preshared_key() -> str:
return subprocess.check_output(["wg", "genpsk"]).decode().strip()
def build_client_config(
client_private_key: str,
client_vpn_ip: str, # 例: "10.8.0.3"
server_public_key: str,
server_endpoint: str, # 例: "home.example.com:51820"
allowed_ips: str = "192.168.1.0/24",
dns: str = "",
) -> str:
dns_line = f"DNS = {dns}\n" if dns else ""
return f"""[Interface]
PrivateKey = {client_private_key}
Address = {client_vpn_ip}/32
{dns_line}
[Peer]
PublicKey = {server_public_key}
Endpoint = {server_endpoint}
AllowedIPs = {allowed_ips}
PersistentKeepalive = 25
"""
def build_server_peer_block(
client_public_key: str,
client_vpn_ip: str,
comment: str = "",
) -> str:
comment_line = f"# {comment}\n" if comment else ""
return f"""
{comment_line}[Peer]
PublicKey = {client_public_key}
AllowedIPs = {client_vpn_ip}/32
"""
秘密鍵をソース管理に入れないでください。このスクリプトを使用する場合、鍵素材はパーミッション600のファイルに書き込み、決してログ出力や表示をしないでください。
pfSense / OPNsense WireGuard
# pfSense: VPN → WireGuard → トンネル追加
インターフェース鍵:生成(鍵ペアを自動生成)
リッスンポート:51820
インターフェースアドレス:10.8.0.1/24
# ピア追加(クライアントごとに1つ):
公開鍵:<クライアントの公開鍵>
許可IP:10.8.0.2/32
# WireGuardインターフェースを割り当て:
インターフェース → 割り当て → 追加(wg0を選択)
インターフェースを有効化、IPは不要(トンネル設定で設定済み)
# ファイアウォールルール:
WAN → UDPポート51820のインバウンドを許可(クライアントがサーバーに到達できるように)
WireGuardインターフェース → アクセスしたいLANネットワークへのトラフィックを許可
ホームサーバー向けDDNS(ダイナミックDNS)
ほとんどのホームインターネット接続は動的IPを持っています。DDNSを使用することで、IP変更後もVPNエンドポイントに到達可能な状態を維持できます。
# オプション1:Cloudflare DDNS — 認証情報はシークレットファイルに保存し、インラインでは記述しない
# docker-composeエントリ(envファイルを使用):
ddns-updater:
image: qmcgaw/ddns-updater
env_file: ./ddns.env # zone_idとtokenはここに保存、compose内には記述しない
restart: unless-stopped
# ddns.env(chmod 600、gitにコミットしない):
# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
# オプション2:DuckDNS(無料、シンプル)
duckdns.orgにサインアップ → トークンとサブドメイン(myhome.duckdns.org)を取得
トークンを /etc/ddns.env(モード600)に保存し、root所有の小さなスクリプトを使用:
# /usr/local/bin/update-duckdns
#!/bin/sh
set -eu
. /etc/ddns.env
curl --fail --silent --show-error --max-time 10 \
--get "https://www.duckdns.org/update" \
--data-urlencode "domains=myhome" \
--data-urlencode "token=${DUCKDNS_TOKEN}" \
--data-urlencode "ip="
# Cronジョブ:
*/5 * * * * /usr/local/bin/update-duckdns >/dev/null 2>&1
トラブルシューティング
# WireGuardのステータスと最終ハンドシェイクを確認
sudo wg show
# 「latest handshake」が表示されないか非常に古い場合、トンネルは接続されていません。
# 確認事項:
# 1. ルーター/ファイアウォールでUDPポート51820が開いていますか?
sudo ufw status # またはpfSense/UniFiのファイアウォールルールを確認
# 2. クライアント設定のサーバー公開鍵は正しいですか?
sudo wg show wg0 public-key # クライアント設定と比較
# 3. サーバーでIP転送が有効になっていますか?
cat /proc/sys/net/ipv4/ip_forward # 1であるべき
# 4. クライアントのAllowedIPsは、アクセスしようとしているIPをカバーしていますか?
# AllowedIPs = 192.168.1.0/24で192.168.3.5にアクセスしようとするとルーティングされません。
# カーネルログでWireGuardエラーを確認
dmesg | grep wireguard
# WireGuardの再起動
sudo wg-quick down wg0 && sudo wg-quick up wg0
アンチパターン
# 悪い例:秘密鍵をバージョン管理システムに保存したり共有したりする
# 秘密鍵はパスワードと同等 — 決してgitにコミットしてはいけません
# 悪い例:モバイルでAllowedIPs = 0.0.0.0/0を影響を考慮せずに使用する
# フルトンネルはすべてのモバイルトラフィックを自宅のアップロード経由でルーティング — 通常は低速
# 悪い例:モバイルクライアントでPersistentKeepaliveを設定しない
# NATの背後にあるモバイルクライアントは、これがないとアイドルトンネルが切断されます
# 悪い例:ファイアウォールでポート51820を開くが、サーバーでIP転送を忘れる
# トンネルは接続されるがトラフィックはルーティングされない — デバッグが紛らわしい
# 悪い例:複数のクライアントデバイスで鍵ペアを共有する
# 各デバイスは独自の一意の鍵ペアを持つ必要がある — 共有鍵はセキュリティモデルを破壊します
# 悪い例:ブロードな「FORWARD ACCEPT」iptablesルールを使用する
# 転送ルールはwg0インターフェースと方向にのみスコープ指定する
ベストプラクティス
- クライアントデバイスごとに一意の鍵ペアを生成 — 鍵を再利用しない
- モバイルではスプリットトンネリング(
AllowedIPs = <home subnets>)を使用 - すべてのモバイルクライアントで
PersistentKeepalive = 25を設定 - ISPが動的IPを割り当てる場合はDDNSを使用。認証情報は環境ファイルに保存し、インラインには記述しない
- スコープ指定のiptables転送ルール(wg0のインバウンドのみ)を、汎用的なFORWARD ACCEPTの代わりに使用
- クライアント設定にPi-holeのIPを
DNS =として追加し、VPN経由で広告ブロックを実現 - サーバーの鍵ペアを定期的にローテーションし、すべてのクライアント設定を更新
関連スキル
- homelab-network-setup
- homelab-vlan-segmentation
- homelab-pihole-dns