家庭实验室 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 = <粘贴服务器私钥>
# 范围限定的转发规则:允许 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。
Internet 流量(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 以便在 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 注册 → 获取 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 转发
# 隧道连接成功但没有流量路由 — 排查困难
# 错误:在多个客户端设备之间共享一个密钥对
# 每个设备必须拥有自己唯一的密钥对 — 共享密钥破坏安全模型
# 错误:使用宽泛的 iptables 规则 "FORWARD ACCEPT"
# 将转发规则限定到 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