群晖 NAS 异地组网全攻略:OpenVPN异地组网

群晖 NAS 异地组网全攻略:OpenVPN + Docker 打通两地内网(从零部署)

一、部署目的

很多朋友有这样的需求:两处办公点或家庭网络需要像在同一局域网一样互相访问。比如 A 地办公室的电脑要直接访问 B 地仓库的打印机、NAS、监控,或者人在外面时用手机连回两地内网。

本教程基于两台群晖 NAS(分别位于 A、B 两地),通过 Docker 运行 OpenVPN 实现:

  • 两地局域网互通:A 地与 B 地设备互相访问
  • 移动端接入:手机在外网通过 VPN 访问两地内网
  • IPv6 公网传输:利用 NAS 的公网 IPv6 建立隧道(无公网 IPv4 也可)
  • TCP 协议:降低运营商 QoS 影响
  • 流量伪装:非标端口 + tls-crypt,降低被识别封杀风险
  • 证书 5 年有效:免去频繁重新签发的麻烦

说明:本文使用经典的 kylemanna/openvpn 镜像(OpenVPN 2.4.9)。虽然该镜像多年未更新,但它是目前唯一能同时满足 site-to-site、TCP、IPv6、自定义证书有效期、完全自定义配置等全部需求的成熟方案。若追求最新版本,可参考文末的替代方案。

二、前置准备

2.1 环境要求

  • 两台群晖 NAS(A 地、B 地),均已安装 Container Manager(Docker)
  • 已开启 SSH:控制面板 → 终端机和 SNMP → 启用 SSH
  • 群晖拥有公网 IPv6 地址
  • 群晖配置了 IPv6 DDNS

本文约定:

项目A 地B 地
局域网网段192.168.A.0/24192.168.B.0/24
NAS IP192.168.A.NAS192.168.B.NAS
路由器192.168.A.1192.168.B.1
DDNS 域名vpn.yourdomain.com
角色ServerClient

请根据你的实际情况替换 ABNAS 等占位符。

2.2 验证网络环境

① 确认公网 IPv6(不能是 fe80: 开头的链路本地地址):

ip -6 addr show

正常的公网 IPv6 以 2001:240e:2409: 等开头。

② 确认 DDNS 解析到 IPv6

nslookup vpn.yourdomain.com

应看到一条 2409:... 之类的 IPv6 记录。

注意:如果你的 NAS 只拿到 fe80: 开头的链路本地地址,那是无法跨公网通信的,必须从路由器确认已下发公网 IPv6 前缀(SLAAC / DHCPv6)。

2.3 拉取镜像(两台都要)

docker pull kylemanna/openvpn

三、核心概念:OpenVPN 与网络层面的关系

在动手前,先理解 OpenVPN 在 site-to-site 组网中扮演的角色,这决定了后面所有配置。

3.1 OpenVPN 是”虚拟路由器”

OpenVPN 隧道建立后,VPN 服务端和客户端可以看成两台路由器。它们各自连接自己的局域网,中间通过加密隧道相连。

A地局域网(192.168.A.0/24)          B地局域网(192.168.B.0/24)
      │                                   │
 群晖NAS-A(server)                群晖NAS-B(client)
      └────────── VPN隧道(192.168.255.0/24) ──────────┘

3.2 要实现互通,需要三层配置

  1. OpenVPN 本身:隧道建立、证书认证
  2. 内核 IP 转发:NAS 允许把收到的包转发到另一个接口
  3. 路由/NAT:两端局域网设备知道”去对端网段”该走哪台 NAS

很多教程只讲 OpenVPN 配置,不讲后两层,导致”隧道通了但访问不了”——这是最常见的坑。

四、A 地 Server 端部署

4.1 创建配置目录

sudo mkdir -p /opt/openvpn

4.2 生成 server 配置(TCP over IPv6)

docker run -v /opt/openvpn:/etc/openvpn --rm \
  kylemanna/openvpn ovpn_genconfig \
  -u tcp6://vpn.yourdomain.com \
  -p "route 192.168.B.0 255.255.255.0"

参数说明:

  • -u tcp6://...:TCP over IPv6 传输,地址用 DDNS 域名
  • -p "route 192.168.B.0 ...":推送 B 地路由给所有客户端

4.3 初始化 PKI(证书 5 年有效期)

这是最关键的一步,关系到”5 年不出现验证失败”:

docker run -v /opt/openvpn:/etc/openvpn --rm -it \
  -e EASYRSA_CERT_EXPIRE=1825 \
  -e EASYRSA_CA_EXPIRE=1825 \
  -e EASYRSA_CRL_DAYS=1825 \
  kylemanna/openvpn ovpn_initpki

注意(证书有效期):要保证 5 年不出验证失败,必须把 CA、Server、Client 证书有效期全部设为 5 年(1825 天)。只设 EASYRSA_CERT_EXPIRE 不够,还要 EASYRSA_CA_EXPIRE

sudo openssl x509 -in /opt/openvpn/pki/ca.crt -noout -dates

确认 notAfter 是 5 年后。

注意(证书文件名):生成的 server 证书文件名是以 commonName 命名(如 vpn.yourdomain.com.crt),不是 server.crt。这是正常的,镜像会自动识别。

4.4 修改配置文件

编辑 /opt/openvpn/openvpn.conf

① 非标端口(伪装):

port 54443

② 升级混淆 tls-auth → tls-crypt:

tls-crypt /etc/openvpn/pki/ta.key

③ 推送两条内网路由:

### Push Configurations Below
push "comp-lzo no"
push "route 192.168.A.0 255.255.255.0"
push "route 192.168.B.0 255.255.255.0"

4.5 开启内核 IP 转发

这是很多人漏掉的关键步骤。OpenVPN 服务端必须开启内核转发,否则客户端无法通过服务端访问内网:

# 临时生效
sudo sysctl -w net.ipv4.ip_forward=1

# 永久生效(群晖需通过开机脚本,见下文)

注意(持久化):群晖的 /etc/sysctl.conf 在重启后会被覆盖。要让 ip_forward=1 永久生效,需要用到开机脚本/usr/local/etc/rc.d/ 下的自启动脚本),或通过 控制面板 → 任务计划 添加开机自启任务。

4.6 启动 server 容器

docker run -d \
  --name openvpn-a \
  --network host \
  --cap-add NET_ADMIN \
  --device /dev/net/tun \
  -v /opt/openvpn:/etc/openvpn \
  --restart unless-stopped \
  kylemanna/openvpn

验证启动:

docker logs openvpn-a

正常日志关键行:

Listening for incoming TCP connection on [AF_INET6][undef]:54443
Initialization Sequence Completed

注意(host 网络模式):必须用 --network host,因为需要 OpenVPN 直接操作宿主机的 tun 接口和路由。同时 --cap-add NET_ADMIN--device /dev/net/tun 缺一不可。

注意(停止时路由清理报错):容器停止时会出现 RTNETLINK answers: Operation not permitted,这是 host 模式下权限限制,不影响运行,可忽略

4.7 A 地 NAT 转发

kylemanna/openvpn 镜像默认会自动添加 NAT 规则(将隧道网段 192.168.255.0/24 做 MASQUERADE)。验证:

sudo iptables -t nat -L -n

应能看到:

MASQUERADE  all  --  192.168.255.0/24  0.0.0.0/0

五、签发客户端证书

5.1 签发 B 地客户端证书

docker run -v /opt/openvpn:/etc/openvpn --rm -it \
  -e EASYRSA_CERT_EXPIRE=1825 \
  kylemanna/openvpn easyrsa build-client-full clientB nopass

导出配置:

docker run -v /opt/openvpn:/etc/openvpn --rm \
  kylemanna/openvpn ovpn_getclient clientB > /opt/openvpn/clientB.ovpn

5.2 签发移动端证书

docker run -v /opt/openvpn:/etc/openvpn --rm -it \
  -e EASYRSA_CERT_EXPIRE=1825 \
  kylemanna/openvpn easyrsa build-client-full mobile nopass

导出配置:

docker run -v /opt/openvpn:/etc/openvpn --rm \
  kylemanna/openvpn ovpn_getclient mobile > /opt/openvpn/mobile.ovpn

5.3 修正导出的客户端配置(必做)

注意(端口错误)ovpn_getclient 生成的客户端配置端口还是默认 1194,不会同步你改的 54443,必须手动改成 54443。

注意(tls-auth 残留)ovpn_getclient 生成的客户端配置仍是 tls-auth,不会同步 server 的 tls-crypt,必须手动改成 tls-crypt

手动修正每个 .ovpn 文件:

  • 端口 119454443
  • 只保留 remote vpn.yourdomain.com 54443 tcp6 一行
  • <tls-auth><tls-crypt></tls-auth></tls-crypt>
  • 删除 key-direction 1

注意(tls-crypt 标签格式,最坑):手动改时,开始标签必须是 <tls-crypt>(没有斜杠)。如果误写成 </tls-crypt>(带斜杠),客户端会报错:

UNKNOWN/unsupported OPTIONS:'', '-----BEGIN', ...

因为客户端找不到开始标签,把整块当普通文本逐行解析。同时建议删掉块内的注释行和空行,避免 OpenVPN 3 客户端解析报错。

六、移动端接入与测试

6.1 导入手机

安卓手机安装 OpenVPN Connect,导入修正后的 mobile.ovpn

6.2 测试

连接成功后,手机拿到隧道 IP(如 192.168.255.6)。

如果”内网能 ping 到手机,但手机 ping 不到内网”,问题出在回包路由

注意(回包路由,site-to-site 最常见问题):手机访问 A 地内网设备时,A 地设备的回包发往 192.168.255.6(隧道 IP),但 A 地内网设备/路由器不知道这个网段该走哪,导致回包丢失。

解决方案:在 A 地路由器加静态路由:

目标网段:192.168.255.0/24
子网掩码:255.255.255.0
下一跳:192.168.A.NAS(A地NAS)

七、B 地 Client 端部署

7.1 传输配置文件

把修正好的 clientB.ovpn 传到 B 地,放到 /opt/openvpn/

7.2 启动 client 容器

# 把 clientB.ovpn 复制为 openvpn.conf
cp /opt/openvpn/clientB.ovpn /opt/openvpn/openvpn.conf

docker run -d \
  --name openvpn-b \
  --network host \
  --cap-add NET_ADMIN \
  --device /dev/net/tun \
  -v /opt/openvpn:/etc/openvpn \
  --restart unless-stopped \
  kylemanna/openvpn

7.3 开启内核转发 + NAT

B 地也要开启 IP 转发,并添加 NAT 让 B 地局域网能访问 A 地:

sudo sysctl -w net.ipv4.ip_forward=1

# 让 B 地局域网(192.168.B.0/24)访问 A 地时,经隧道转发并做 NAT
sudo iptables -t nat -A POSTROUTING -s 192.168.B.0/24 -o tun0 -j MASQUERADE

注意(B 地 NAT 是必须的):B 地局域网设备访问 A 地时,源地址是 192.168.B.x,A 地不知道这个网段。B 地 NAS 必须做 SNAT/MASQUERADE,把源地址伪装成隧道 IP,A 地的回包才能正确返回。

八、双向互通:静态路由配置(核心)

这是实现”两地局域网互访”最关键的一步。OpenVPN 隧道建立后,NAS 相当于路由器,但两地的内网设备/路由器必须知道”去对端网段该走哪台 NAS”

8.1 A 地路由器静态路由

登录 A 地路由器,添加:

目标网段:192.168.B.0/24
子网掩码:255.255.255.0
下一跳:192.168.A.NAS(A地NAS)

同时(如果移动端要访问 A 地,前面已提过):

目标网段:192.168.255.0/24
子网掩码:255.255.255.0
下一跳:192.168.A.NAS(A地NAS)

8.2 B 地路由器静态路由

登录 B 地路由器,添加:

目标网段:192.168.A.0/24
子网掩码:255.255.255.0
下一跳:192.168.B.NAS(B地NAS)

注意(为什么必须在路由器加路由):如果在每台电脑上加路由,主机一多就非常麻烦。推荐在路由器上配,一次性解决整个网段的访问。配置后,A 地任何设备访问 192.168.B.x 时,流量会先到 A 地路由器,路由器根据静态路由转发给 A 地 NAS,NAS 再经隧道转发到 B 地。

注意(B 地作为 client,server 端需要知道 B 地网段):如果你的 server 配置里没有推送 B 地网段的路由,或者 B 地有多个网段,可能需要用到 CCD(client-config-dir) 配合 iroute 指令。

当 B 地 client 连接时,在 A 地 server 的 ccd/clientB 文件里写:

iroute 192.168.B.0 255.255.255.0

这告诉 A 地 server:”clientB 后面挂着 192.168.B.0/24 这个网段”,A 地 NAS 才知道要把发往 B 地的包转发进隧道。

但注意:如果你用 push "route ..." 全局推送,且 B 地只有这一个网段,通常不需要 CCD。只有当你需要区分不同客户端访问不同网段时才需要。

九、验证与排错

9.1 验证隧道

A 地 server 日志确认 clientB 已连接:

docker logs openvpn-a | grep clientB

9.2 验证互通

① 从 A 地 NAS ping B 地内网设备:

ping 192.168.B.NAS

② 从 B 地 NAS ping A 地内网设备:

ping 192.168.A.NAS

③ traceroute 确认路径:

traceroute 192.168.B.NAS

正常路径应经过:A地NAS → 隧道 → B地NAS → B地设备。

9.3 常见排错

现象可能原因排查
隧道连不上端口/协议不匹配确认客户端端口 54443、tcp6
连上但访问不了内网内核转发未开 / 静态路由缺失cat /proc/sys/net/ipv4/ip_forward 应为 1
单向通(内网→手机通,反向不通)回包路由缺失路由器加静态路由
两端局域网都不通路由器静态路由缺失 / NAT 缺失检查两地路由器 + NAS 的 NAT

十、架构总结

A地局域网 192.168.A.0/24            B地局域网 192.168.B.0/24
     │  (A地路由器静态路由)                │  (B地路由器静态路由)
     │  B.0/24 → A.NAS                   │  A.0/24 → B.NAS
群晖NAS-A (A.NAS, server)          群晖NAS-B (B.NAS, client)
     └────────── VPN隧道(192.168.255.0/24) ──────────┘
                    │
             移动端 (client)

十一、安全与运维提醒

  1. 镜像停更风险kylemanna/openvpn 停留在 2.4.9(2020 年),OpenSSL 较老。若追求新版本,可考虑:
    • 自行构建基于 OpenVPN 2.6 的镜像
    • 或使用持续维护的社区镜像(需评估其 site-to-site + IPv6 + 自定义证书能力)
  2. 证书权限:收紧私钥权限: sudo chmod 600 /opt/openvpn/pki/private/*.key sudo chmod 600 /opt/openvpn/pki/ta.key
  3. IPv6 地址变化:公网 IPv6 可能是动态的,务必保证 DDNS 更新及时,否则客户端通过域名可能连不到。
  4. 防火墙:如果群晖启用了自带防火墙(控制面板 → 安全性 → 防火墙),需放行 54443 端口(TCP)和 tun0 接口的转发流量。

希望这篇教程能帮到有同样异地组网需求的朋友。如果你在部署中遇到问题,欢迎在评论区留言交流!

本文由屑鱼不吃猫创作,转载请标注文章出处
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇