部署配方 · 网络与访问
用 wg-easy 搭 WireGuard:不开任何端口就能从外面回家
家庭机架上跑 VPN 的意义不是匿名,而是不必把 Jellyfin、Nextcloud 和 Immich 暴露在公网上。一个 UDP 端口加一个内核模块,顶替掉四个转发的 TCP 端口和四个全世界都能砸的登录页面。wg-easy 是 WireGuard 之上最薄的一层:一个生成对端配置、显示握手时间的网页界面,加上隧道本身。
| 容器镜像 | ghcr.io/wg-easy/wg-easy:15.0.1 |
|---|---|
| 宿主端口 | :51820/udp, :51821/tcp |
| 数据目录 | /srv/homelab/wg-easy/data |
| 内存预留 | 40 MB |
| CPU 上限 | 0.15 vCPU (cpus: "0.15") — the kernel does the work, not the container |
| 更新节奏 | once or twice a year, pinned tag. Breaking changes are rare but the config file format has changed between major versions |
| ARM 兼容 | arm64 native; the container ships the wireguard-tools userspace, the kernel module comes from the host |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :51820 | 51820 | udp | 对公网 | the tunnel itself; the only UDP port forwarded at the router |
| :51821 | 51821 | tcp | 仅局域网 | web UI for creating client configs and reading handshake times |
部署步骤
在宿主机上生成后台密码哈希
容器接受的是 bcrypt 哈希,不是明文密码。用镜像自身来生成,可以避免本机 bcrypt 和容器里的版本不匹配。
run docker run --rm ghcr.io/wg-easy/wg-easy:15.0.1 wgpw 'the-password' # 输出 PASSWORD_HASH='$2a$12$...',把这一行写进 /srv/homelab/.env只转发一个端口,别的都不转
51820/udp 指向树莓派,51821 保持关闭。如果你在运营商大内网后面没有公网地址,这一步就是做不了的,需要改用中继方案。
run # 路由器:UDP 51820 -> 192.168.10.20 # 从外网验证:nc -uzv your.public.ip 51820启动容器并创建第一个客户端
第一份客户端配置在界面里生成。手机用二维码导入,笔记本用下载的 .conf 配合官方客户端。
run cd /srv/homelab/wg-easy && docker compose up -d docker logs --tail 20 wg-easy docker exec wg-easy wg show验证拆分隧道真的是你以为的样子
手机上隧道开着时,访问树莓派的局域网地址应该通,而访问查公网 IP 的网站应该显示你移动运营商给的地址,而不是家里的。如果显示的是家里的地址,说明 AllowedIPs 比你以为的宽。
run docker exec wg-easy wg show wg0 allowed-ips # 客户端应是 10.8.0.2/32,而客户端侧只应路由局域网段把数据目录另存一份
服务端密钥和所有客户端配置都在一个目录里。每次改动后复制一次,同时导出一个压缩包放进密码管理器——当坏掉的正是那台派时,救你的是那个压缩包。
run tar czf /srv/homelab/backup/wg-easy-$(date +%F).tgz -C /srv/homelab/wg-easy data
转发一个端口,而不是四个
在此之前,机架转发了四个端口:443 给反向代理,另加三个给家人想在外面用到的应用。它们每一个都是暴露在公网上的登录页,而日志显示扫描在几小时内就开始了。
现在只转发一个 UDP 端口。端口扫描什么都发现不了,因为未通过认证的 WireGuard 报文根本不产生任何回应——这一点和 HTTPS 登录页截然不同,后者会给你一个 200 和一个密码表单。
| 做法 | 开放端口 | 扫描器看到什么 |
|---|---|---|
| 每个应用各自转发 | 443 加 3 个应用端口 | 四个可以拿来撞密码的登录页 |
| 全部走反向代理 | 80 加 443 | 一个代理,仍然是公开攻击面 |
| WireGuard + 应用只在局域网 | 443 加 51820/udp | 一个 Web 服务器,UDP 上是一片安静 |
默认拆分隧道
WG_ALLOWED_IPS 设成局域网段加 VPN 网段,而不是 0.0.0.0/0。也就是说手机只把访问机架的流量送进隧道,其它流量直接走移动网络——更快、更省电,也不会让这台派成为别人视频通话的瓶颈。
只有两台设备拿到全局隧道配置:常在咖啡馆 wifi 上用的笔记本,以及出差带的平板。这两台的目的是加密出口,不是访问内网。
隧道内的 DNS,才是真正有用的部分
把 WG_DEFAULT_DNS 指向 Pi-hole 的地址,隧道才不只是个文件通道:广告拦截跟着手机上了移动网络,而那些内外有别的域名(jellyfin.lan、cloud.lan)在任何地方都能解析。
需要知道的故障模式是:手机上开着常驻隧道走进一个有强制门户的网络,所有请求都会失败,直到你过了门户页。给会出行的手机设成按需连接,而不是始终开启。
docker exec wg-easy wg show
# 最近两分钟内有握手,说明这个对端现在连着吊销权限是没人测试的那个功能
一设备一份配置而不是全家共用一份,理由就是吊销。手机丢了,代价应该是删掉一份,而不是给全家重新发一遍。
测一次:删掉一份配置,确认 wg show 里对端消失,再确认那台手机什么都连不上。五分钟的事,而这是唯一能证明你的吊销真的生效的方式。
compose 文件
整份文件放在 /srv/homelab/wg-easy/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15.0.1
container_name: wg-easy
restart: unless-stopped
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
environment:
TZ: Asia/Shanghai
WG_HOST: rack.twinkling.top
PASSWORD_HASH: ${WG_UI_HASH}
WG_PORT: "51820"
WG_DEFAULT_ADDRESS: 10.8.0.x
WG_DEFAULT_DNS: 192.168.10.20
WG_ALLOWED_IPS: 192.168.10.0/24,10.8.0.0/24
WG_PERSISTENT_KEEPALIVE: "25"
volumes:
- /srv/homelab/wg-easy/data:/etc/wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv4.ip_forward=1
networks: [rack]
networks:
rack:
external: true加固清单
- 后台只在局域网可达,并在代理层再加一道基本认证,从不做端口转发
- 一台设备一个客户端配置,并且命名,这样手机丢失时只需删掉对应的那一份
- 隧道内 DNS 指向 Pi-hole 的地址,出行时拦截同样生效
- 只该访问机架的设备把 AllowedIPs 收窄到局域网网段,只有两台需要公共 wifi 隐私的设备走全局隧道
备份方案
数据目录里存着服务端密钥对和所有客户端配置,所以每次改动后都复制一次,并导出成压缩包放进密码管理器。丢了它意味着每台手机和笔记本都要重发一份配置——二十分钟的活,而你绝对不想在旅途中干这件事。
上线后验证
- wg show 里能看到你正在测试的那台设备两分钟内有过握手
- 手机在移动网络下能打开 jellyfin.lan,并且看不到广告,说明 DNS 走的是隧道
- 删掉一个对端后该设备立刻断开,而且是在设备上验证的,不是只看界面
- 51821 端口从家外访问不通
踩过的坑
- WG_HOST 必须是客户端将要连接的公网域名或 IP,而不是局域网地址。填错的后果是这份配置只在屋里能用——正好和目的相反。
- 手机连着需要过门户页的 wifi,又开着常驻隧道,就加载不了门户页,因为门户不在 AllowedIPs 里。请用按需连接。
- 内核模块是从宿主机加载的。跑在没有编译 wireguard 模块的主线内核上时,容器能启动但隧道永远起不来——先看 lsmod | grep wireguard。
- 把一份客户端配置复制到第二台设备上照样能用,但吊销就失效了。永远一设备一份。
硬件与选型问答
- 用 WireGuard 还是 Tailscale?
- 自己控制路由器、又不想有第三方在链路里,就用 WireGuard;在运营商大内网后面、想零配置、并且能接受依赖别人的协调服务器,就用 Tailscale。本机架两个都有:WireGuard 给家里人用,Tailscale 装在一个节点上给一位远程家人用。
- 在外面用 VPN 看 Jellyfin 会变慢吗?
- Pi 4 上 WireGuard 的吞吐大约 90Mbps,够一路 1080p 直连播放,不够 4K 原盘。如果远程观看是主要用途,诚实的做法是把码率压在 10Mbps 以下,而不是指望这台派推更多。
- 这个占多少内存?
- 大约 40MB。容器只是个控制面,加密在内核里完成,所以资源开销并不会像很多人以为的那样随流量增长。