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:port)。
流量是端對端加密的,沒有中央伺服器或憑證授權單位。
伺服器設定(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 = <貼上伺服器私鑰>
# 限定範圍的轉發規則:允許 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 = <手機公鑰>
AllowedIPs = 10.8.0.2/32
[Peer]
# 筆電 — 替換為實際的筆電公鑰
PublicKey = <筆電公鑰>
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 = <手機私鑰>
Address = 10.8.0.2/32
DNS = 192.168.1.2 # 可選:使用 Pi-hole 在隧道中進行 DNS
[Peer]
PublicKey = <伺服器公鑰>
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
# 新增對等節點(每個客戶端一個):
公鑰:<客戶端公鑰>
允許的 IP:10.8.0.2/32
# 指定 WireGuard 介面:
介面 → 指定 → 新增(選擇 wg0)
啟用介面,不需要 IP(已在隧道設定中設定)
# 防火牆規則:
WAN → 允許 UDP 連接埠 51820 傳入(以便客戶端可以連線到伺服器)
WireGuard 介面 → 允許流量到您想要存取的 LAN 網路
家庭伺服器的 DDNS(動態 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 狀態和最後一次握手
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 錯誤
sudo 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 = <家庭子網路>) - 在所有行動客戶端上設定
PersistentKeepalive = 25 - 如果您的 ISP 分配動態 IP,請使用 DDNS;將憑證儲存在 env 檔案中,而非內嵌
- 使用限定範圍的 iptables 轉發規則(僅限於 wg0 的傳入流量),而不是全面的 FORWARD ACCEPT
- 在客戶端設定中加入 Pi-hole 的 IP 作為
DNS =,以在 VPN 上獲得廣告阻擋功能 - 定期輪換伺服器金鑰對並更新所有客戶端設定
相關技能
- homelab-network-setup
- homelab-vlan-segmentation
- homelab-pihole-dns