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 = <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 → 新增隧道
介面金鑰:生成(自動建立金鑰對)
監聽連接埠: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 錯誤
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