homelab-wireguard-vpn

homelab-wireguard-vpn

熱門

WireGuard VPN 伺服器建置、對等點(Peer)設定、金鑰生成、分流通道(Split Tunneling)與全通道(Full Tunnel)路由設定,以及從手機與筆記型電腦等用戶端遠端存取家庭網路。

24萬星標
3.6萬分支
更新於 2026/8/3
SKILL.md
唯讀
名稱
homelab-wireguard-vpn
描述

WireGuard VPN 伺服器建置、對等點(Peer)設定、金鑰生成、分流通道(Split Tunneling)與全通道(Full Tunnel)路由設定,以及從手機與筆記型電腦等用戶端遠端存取家庭網路。

Homelab WireGuard VPN

WireGuard 是快速且現代化的 VPN 協定。它是遠端存取家庭網路的最佳選擇——比 OpenVPN 更容易設定,且速度比大多數替代方案更快。

所有設定範例均展示了常見的配置方式。在將命令套用到您的系統之前,請仔細檢查每條命令——特別是 iptables 轉發規則和金鑰檔案權限——並建議在維護時間區段內進行變更。

使用時機

  • 在 Raspberry Pi、Linux 主機、pfSense 或路由器上建置 WireGuard 伺服器
  • 生成 WireGuard 金鑰對並撰寫對等點(Peer)設定檔
  • 設定手機或筆記型電腦遠端存取家庭網路
  • 解釋分流通道(Split Tunneling,僅路由家庭網路流量)與全通道(Full Tunnel,路由所有流量)的差異
  • 排除無法連線的 WireGuard 連線故障
  • 自動化多個用戶端的對等點設定檔生成

WireGuard 運作原理

您的手機(WireGuard 用戶端)
    │
    │  加密 UDP 通道(連接埠 51820)
    │
您的家用路由器(WireGuard 伺服器 — 需要實體公用 IP 或 DDNS)
    │
您的家庭網路(192.168.1.0/24、NAS、Pi 等)

每台設備都有一組金鑰對(公鑰 + 私鑰)。
伺服器知道每個用戶端的公鑰。
用戶端 know 伺服器的公鑰與端點(IP:Port)。
流量進行端到端加密,無需中央伺服器或憑證簽發機構(CA)。

伺服器建置 (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 廣告攔截。
  缺點:家中的上傳速度會成為您在任何地方的效能瓶頸。

# 多子網路分流通道(最常見的 Homelab 使用情境):
  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_key, public_key)。"""
    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 → Add Tunnel
  Interface Keys: Generate(自動生成金鑰對)
  Listen Port: 51820
  Interface Address: 10.8.0.1/24

# 新增對等點 Add Peer(每個用戶端一個):
  Public Key: <client public key>
  Allowed IPs: 10.8.0.2/32

# 指派 WireGuard 介面:
  Interfaces → Assignments → Add(選擇 wg0)
  啟用介面,無需設定 IP(已在 Tunnel 設定中指定)

# 防火牆規則:
  WAN → 允許 UDP port 51820 入站(讓用戶端可以連線至伺服器)
  WireGuard 介面 → 允許流量通往您希望可存取的 LAN 網路

家庭伺服器的 DDNS (Dynamic DNS)

大多數家庭網際網路連線都使用動態 IP。使用 DDNS 可確保您的 VPN 端點在 IP 變更後仍可正常連線。

# 選項 1:Cloudflare DDNS — 將憑證儲存在 secrets 檔案中,而非內嵌在設定檔
# 使用 env 檔案的 docker-compose 設定項目:
  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 註冊 → 取得 token 和子網域(myhome.duckdns.org)
  將 token 儲存在 /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 狀態與上次握手(Handshake)時間
sudo wg show

# 若 "latest handshake" 顯示為 never 或時間很久以前,代表通道未連線。
# 請檢查:
# 1. 路由器/防火牆上的 UDP port 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,則無法順利路由。

# 檢查核心紀錄(kernel log)中的 WireGuard 錯誤訊息
dmesg | grep wireguard

# 重啟 WireGuard
sudo wg-quick down wg0 && sudo wg-quick up wg0

反模式

# 錯誤做法:將私鑰儲存在版本控制中或進行分享
# 私鑰等同於密碼 — 切勿提交至 git

# 錯誤做法:在行動裝置上使用 AllowedIPs = 0.0.0.0/0 卻未考量影響
# 全通道會將行動裝置的所有流量導向家中的上傳頻寬 — 通常速度較慢

# 錯誤做法:未在行動用戶端上設定 PersistentKeepalive
# 位於 NAT 後方的行動用戶端若未設定此項,閒置的通道會中斷連線

# 錯誤做法:在防火牆開啟 Port 51820,卻忘記在伺服器上啟用 IP 轉發
# 通道看似已連線但無法路由流量 — 除錯時容易令人困惑

# 錯誤做法:在多個用戶端設備之間共用同一組金鑰對
# 每台設備都必須擁有唯一的金鑰對 — 共用金鑰會破壞安全模型

# 錯誤做法:使用過於寬鬆的 "FORWARD ACCEPT" iptables 規則
# 應將轉發規則嚴格限定在 wg0 介面與指定方向

最佳實踐

  • 為每個用戶端設備生成唯一的金鑰對 — 切勿重複使用金鑰
  • 在行動裝置上使用分流通道(AllowedIPs = <家庭網路子網路>
  • 在所有行動用戶端上設定 PersistentKeepalive = 25
  • 若 ISP 分配動態 IP,請使用 DDNS;將憑證儲存在環境變數檔案中,而非內嵌於設定檔
  • 使用限定範圍的 iptables 轉發規則(僅限 wg0 入站),而非全域的 FORWARD ACCEPT
  • 在用戶端設定檔中添加 Pi-hole IP 作為 DNS =,以透過 VPN 享有廣告攔截功能
  • 定期輪替伺服器金鑰對並更新所有用戶端設定檔

相關 Skills

  • homelab-network-setup
  • homelab-vlan-segmentation
  • homelab-pihole-dns