Skip to content
twinkling.top服务WireGuard (wg-easy)EN
板型any Pi 3 and up, including a Zero 2 W
内核6.6.51+rpt-rpi-v8
空闲温度48 °C
内核架构arm64
参考机:Pi 4B 8GB / Bookworm 64-bit

部署配方 · 网络与访问

用 wg-easy 搭 WireGuard:不开任何端口就能从外面回家

家庭机架上跑 VPN 的意义不是匿名,而是不必把 Jellyfin、Nextcloud 和 Immich 暴露在公网上。一个 UDP 端口加一个内核模块,顶替掉四个转发的 TCP 端口和四个全世界都能砸的登录页面。wg-easy 是 WireGuard 之上最薄的一层:一个生成对端配置、显示握手时间的网页界面,加上隧道本身。

ghcr.io/wg-easy/wg-easy:15.0.1 · 2026-09-03

WireGuard (wg-easy) rack plate: board stack and host ports
服务参数
容器镜像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暴露用途
:5182051820udp对公网the tunnel itself; the only UDP port forwarded at the router
:5182151821tcp仅局域网web UI for creating client configs and reading handshake times

部署步骤

  1. 在宿主机上生成后台密码哈希

    容器接受的是 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
  2. 只转发一个端口,别的都不转

    51820/udp 指向树莓派,51821 保持关闭。如果你在运营商大内网后面没有公网地址,这一步就是做不了的,需要改用中继方案。

    run
    # 路由器:UDP 51820 -> 192.168.10.20
    # 从外网验证:nc -uzv your.public.ip 51820
  3. 启动容器并创建第一个客户端

    第一份客户端配置在界面里生成。手机用二维码导入,笔记本用下载的 .conf 配合官方客户端。

    run
    cd /srv/homelab/wg-easy && docker compose up -d
    docker logs --tail 20 wg-easy
    docker exec wg-easy wg show
  4. 验证拆分隧道真的是你以为的样子

    手机上隧道开着时,访问树莓派的局域网地址应该通,而访问查公网 IP 的网站应该显示你移动运营商给的地址,而不是家里的。如果显示的是家里的地址,说明 AllowedIPs 比你以为的宽。

    run
    docker exec wg-easy wg show wg0 allowed-ips
    # 客户端应是 10.8.0.2/32,而客户端侧只应路由局域网段
  5. 把数据目录另存一份

    服务端密钥和所有客户端配置都在一个目录里。每次改动后复制一次,同时导出一个压缩包放进密码管理器——当坏掉的正是那台派时,救你的是那个压缩包。

    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)在任何地方都能解析。

需要知道的故障模式是:手机上开着常驻隧道走进一个有强制门户的网络,所有请求都会失败,直到你过了门户页。给会出行的手机设成按需连接,而不是始终开启。

shell
docker exec wg-easy wg show
# 最近两分钟内有握手,说明这个对端现在连着

吊销权限是没人测试的那个功能

一设备一份配置而不是全家共用一份,理由就是吊销。手机丢了,代价应该是删掉一份,而不是给全家重新发一遍。

测一次:删掉一份配置,确认 wg show 里对端消失,再确认那台手机什么都连不上。五分钟的事,而这是唯一能证明你的吊销真的生效的方式。

compose 文件

整份文件放在 /srv/homelab/wg-easy/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。

compose.yaml
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。容器只是个控制面,加密在内核里完成,所以资源开销并不会像很多人以为的那样随流量增长。