本文最后更新于:2025年7月1日 凌晨
龙哥就是龙!惹啊!
0x00. 一切开始之前 最近手上的各种服务器不知道为啥多了起来,但大都没有公网 IP,又都处在不同的局域网里,笔者的肉身连带自己的笔记本也没有办法同时处在这些不同的网络当中,因此笔者迫切需要一个可用且优雅的异地组网方案,从而让笔者能够无论身处何处都能随时访问不同局域网中的任意一台服务器, 当然现在看来这些服务器大部分情况下的用途似乎就是让笔者闲暇时刻运行 neofetch 和 btop 玩玩
局域网(Local Area Network)也就是我们常说的内网,此类网络通常共用一个连接到互联网(公网)的路由器,共用同一公网 IP,利用网络地址转换(Network Address Translation)技术为内网中的设备与公网服务器建立连接(简而言之就是公网 IP:端口
到内网 IP:端口
整了个映射),因此要与内网中的设备进行通信,我们实质上要实现 内网穿透 ,也叫 NAT 穿透 (NAT traversal)
这个需求的实现其实很简单,我们只需要把不同 LAN 当中的设备组到同一个虚拟网络当中就行—— Virtual Private Network
就是专业干这个的,我们可以使用 VPN 技术在设备间建立点对点的加密信道,从而将不同局域网当中的服务器连接起来,而若要为都处于内网的机器进行打洞,则通常需要有一台位于公网的服务器为他们进行中继:
现有的比较成熟的无需用户自行在公网架设服务器的异地组网服务有 tailscale、zerotier 等,但由于其在中国大陆内通常没有相应的中继服务器,从而导致在国内想要通过此类服务进行连接则往往带有一定的延迟(反正笔者是忍受不了)
如何解决这个问题?不难想到我们可以在公网 VPS 上自行搭建 tailscale 的 DERP 节点,或是自建 zerotier 的 Moon 节点,但既然都要自建节点了, 那么我们为什么不直接自建 VPN 呢 —— 诸如 IPSec、OpenVPN 等老牌 VPN 方案可谓是应有尽有
但相比于这些传统方案,笔者更愿意选择被 Linus 青睐甚至已经合并入 Linux kernel mainline 的 WireGuard —— 这是一个高性能的基于 UDP 的现代 VPN 协议,或许也是目前已知的最好的 VPN 解决方案
本篇博客主要讲述如何利用公网服务器搭建 WireGuard VPN 网络
0x01. 公网服务器基本配置 安装与配置 WireGuard 首先我们在公网服务器上安装 WireGuard,笔者这里是 Ubuntu 24.04
:
参考: WireGuard - Installation
1 2 $ sudo apt-get update && sudo apt-get upgrade -y $ sudo apt-get install -y wireguard
完成安装之后我们会获得一个内核模块,以及相应的用户态 CLI 程序 wg
:
接下来使用 wg
生成一份私钥与对应的公钥,存放在 /etc/wireguard
目录下:
1 $ wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey
接下来创建 WireGuard 配置文件,这里我们放在 /etc/wireguard/wg0.conf
,写入如下内容:
1 2 3 4 5 6 [Interface] PrivateKey = 私钥文件内容ListenPort = 自定义的监听端口Address = 自定义的内网地址,例如 10.0 .0.1 /24 PostUp = iptables -A FORWARD -i wg0 -j ACCEPTPreDown = iptables -D FORWARD -i wg0 -j ACCEPT
这里的 PostUp
与 PreDown
参数分别为接口启动时与关闭时执行的命令,存在如下四个执行节点:
PreUp
:在网络接口创建前执行
PostUp
:在网络接口创建后执行
PreDown
:在网络接口删除前执行
PostDown
: 在网络接口删除后执行
现在我们解析具体命令,其中 wg0
为我们后面要创建的虚拟网卡名,该命令主要是使用 iptables 进行流量转发,在启动 WireGuard 时添加转发规则,在停用时删除转发规则:
iptables -A FORWARD -i wg0 -j ACCEPT
:(默认 filter 表)在 FORWARD 链添加规则——允许从 wg0
接口转发入流量
iptables -A FORWARD -o wg0 -j ACCEPT
:(默认 filter 表)在 FORWARD 链添加规则——允许转发流量出到 wg0
接口
iptables -t nat -A POSTROUTING -o 你的能访问公网的网卡名 -j MASQUERADE
:使用 NAT 表,在 POSTROUTING 链添加规则——对发出的数据做地址伪装使其看起来像从本地网卡发出的数据包, MASQUERADE
即意味着修改出站的包的源地址为该网卡的 ip
如果你不记得 iptables 四表五链,可以简单回忆一下:
为什么不用 nftables?因为👴懒得两个都写了,反正背后都是 netfilter
对于可以访问公网的网卡的名称,可以简单通过如下命令进行获取:
1 $ ip -o -4 route show to default | awk '{print $5}'
接下来使用如下命令启动该接口,其会创建一个虚拟网卡 wg0
:
最后,我们可以为 wg0
接口配置自启动:
1 $ sudo systemctl enable wg-quick@wg0
配置防火墙 为了让 iptables 规则正常工作,我们还需要修改防火墙以允许 IPV4 流量转发,首先编辑 /etc/sysctl.conf
文件,添加如下配置:
也可以在 WireGuard 配置文件中的 [Interface]
添加如下配置来动态开关:
1 2 PostUp = sysctl -w net.ipv4.ip_forward=1 PostDown = sysctl -w net.ipv4.ip_forward=0
然后重新载入配置:
最后开启 wireguard 监听端口上的 UDP 连接:
1 $ sudo ufw allow 你的自定义端口/udp
如果你使用阿里云这样比较专业的云服务器提供商,你可能还需要在相应的控制台页面添加规则:
0x02. 客户端基本配置 Linux 客户端配置 基础配置 首先还是按惯例在本地安装 WireGuard,这里笔者的系统是 openSUSE Tumbleweed
:
1 $ sudo zypper in wireguard-tools
然后按惯例生成公钥与私钥:
1 $ wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey
接下来修改 服务端 配置文件, 添加 客户端配置如下:
每新增一个客户端就添加一份 [Peer]
配置
1 2 3 [Peer] PublicKey = 客户端生成的公钥AllowedIPs = 自定义的内网地址,例如 10.0 .0.2 /32
然后重启 服务端 WireGuard :
1 $ sudo wg-quick down wg0 && sudo wg-quick up wg0
然后在 客户端 创建配置文件 /etc/wireguard/wg0.conf
,写入如下内容:
1 2 3 4 5 6 7 8 9 [Interface] PrivateKey = 客户端生成的私钥Address = 上面配置的客户端地址MTU = 1280 [Peer] PublicKey = 服务端的公钥Endpoint = 服务端的公网地址或是域名:服务端的端口AllowedIPs = 服务端网段,例如 10.0 .0.0 /24
如果你不止想要组网,还想要让 WireGuard 服务端代理客户端的全部流量,只需要配置客户端的 AllowedIPs = 0.0.0.0/0
即可
最后按惯例启动客户端的 WireGuard 即可:
如果有需要,可以配置自启动:
1 $ sudo systemctl enable wg-quick@wg0
配置 DDNS 作为中继的公网服务器的 IP 在某些需求下不一定是恒定的,虽然说域名通常是可以不变的,但有的时候域名对应的 IP 可能会发生改变,而 WireGuard 不会重复进行解析,仅会使用第一次的结果,从而可能造成组网失败 :(
对于 Linux,WireGuard 官方已经提供了 一份自动重新解析 DNS 的脚本 ,我们只需要设置一个定时执行任务即可,例如使用 crontab
:
在 Ubuntu 下,该脚本似乎会默认在 /usr/share/doc/wireguard-tools/examples/reresolve-dns/reresolve-dns.sh
安装一份
1 1 * * * * 脚本所在目录/reresolve-dns.sh wg0 # 每小时执行一次
Windows 客户端配置
不用 Windows 👋
首先从官网 下载对应架构的安装包并安装,选择新建空隧道:
其会自动生成一份私钥和公钥,我们只需要再按照前面的方式填写配置文件即可:
之后在服务端也添加相应配置并重启 WireGuard 即可
0x03. 动态调整服务器的客户端记录 我们不难发现如果每次添加一个新的客户端或删除一个旧的客户端都要在修改完 wg.conf
文件之后重启 WireGuard 似乎有点太愚蠢了(虽然设计上是偏向于预先配置好网络拓扑 所以说优雅在哪 ),因此本节讲述如何在保持服务端的 WireGuard 持续运行的情况下调整服务器上的客户端记录
动态增加新的客户端 我们可以使用如下命令动态添加新的客户端配置:
1 $ sudo wg set wg0 peer 客户端生产的公钥 allowed-ips 自定义的内网地址
不过该配置不会被持久化到文件中,而仅保存在内存中,这意味着下次重新启动就消失了,因此我们需要额外的配置来将其持久化
持久化当前配置 我们已知如下命令可以显示当前配置:
因此我们可以在关闭 WireGuard 时将当前配置持久化到文件当中,编写脚本 /etc/wireguard/wg0.down.sh
如下(别忘了 chmod +x
添加权限):
以及把之前的 iptables 也搬到这个脚本里,方便管理
1 2 3 4 5 6 7 #!/bin/bash wg showconf wg0 | grep -v '^\s*Endpoint\s*=' | tee /etc/wireguard/wg0.autoconf iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o 你的公网网卡名 -j MASQUERADE
之后我们在启动 WireGuard 时将该配置加载进来,编写脚本 /etc/wireguard/wg0.up.sh
如下:
以及把之前的 iptables 也搬到这个脚本里,方便管理
1 2 3 4 5 6 7 #!/bin/bash wg setconf wg0 /etc/wireguard/wg0.autoconf iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o 你的公网网卡名 -j MASQUERADE
然后修改 /etc/wireguard/wg0.conf
如下:
1 2 3 4 5 6 [Interface] PrivateKey = 私钥文件内容ListenPort = 自定义的监听端口Address = 自定义的内网地址,例如 10.0 .0.1 /24 PostUp = /etc/wireguard/wg0.up.shPreDown = /etc/wireguard/wg0.down.sh
0x04. 利用 NGINX 反代 WireGuard Over Trojan
注:这会让速度大幅下降➕延迟大幅提高(毕竟多了好几层传输和加解密的工作),因此除非 Wireguard 直连无法完成配置,否则笔者 其实不太推荐搭建这样的架构
众所周知随着国际网络局势不断变化,前面忘了中间忘了后面也忘了,总而言之就是 WireGuard 直接组网因为各种直接或间接的因素而存在一定的小问题导致无法连接成功,例如笔者学校的防火墙就过滤掉了 WireGuard 流量(也有可能是 UDP 都过滤了,总而言之这导致笔者不能用 tailscale 连接学校服务器很难受),因此需要为我们的 WireGuard 隧道套一些防护
基于 UDP 的 WireGuard 协议容易被各种预期外的物理因素识别与阻断,因此我们不难想到的是我们可以再套几层协议以提高其连接稳定性,在 如何在澳洲校园网玩原神 这一篇博客中笔者简要叙述了使用 NGINX + Websocket + TLS + Trojan 的代理方案,在这里我们同样可以利用类似的架构转发 Wireguard 流量完成组网
注:似乎 WireGuard 在这个架构当中是多余的,也可以直接用 Xray 组网?
公网服务器配置
注:可以通过 CloudFlare CDN 代理隐藏公网服务器真实 IP,但需要注意的是在部分网络线路下 CloudFlare 的部分网段是被屏蔽的,从而导致可用性下降
1️⃣ 配置 WireGuard 和前面的流程基本一样,这里不再赘叙
2️⃣ 配置 NGINX 反向代理 首先是安装与启用 NGINX:
1 2 $ sudo apt-get install -y nginx $ sudo systemctl enable --now nginx.service
接下来在 /etc/nginx/conf.d
目录下创建一个新的配置文件 自己想名字.conf
,写入如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 server { listen 443 ssl; listen [::]:443 ssl; server_name 你的域名; ssl_protocols TLSv1.2 TLSv1.3 ; ssl_certificate 你的 .pem 证书路径; ssl_certificate_key 你的 .key 密钥路径; ssl_session_cache shared:SSL:10m ; ssl_session_timeout 10m ; location / { root /srv/www/你的自定义网页路径; index index.html; } location /和xray配置相同的你的自定义路径 { if ($http_upgrade != "websocket" ) { return 400 ; } proxy_pass http://127.0.0.1:和xray配置相同的你的自定义本地监听端口,例如11451; proxy_intercept_errors on ; error_page 400 = https://你自己的域名/; proxy_http_version 1 .1 ; proxy_set_header Upgrade $http_upgrade ; proxy_set_header Connection "upgrade" ; proxy_read_timeout 600s ; proxy_set_header X-Read-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; } }
最后重启 NGINX:
1 $ sudo systemctl restart nginx.service
3️⃣ 配置 XRAY Trojan 代理 直接上 Docker:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 FROM debian:latestARG DEBIAN_FRONTEND=noninteractiveRUN apt-get update && apt-get upgrade RUN apt-get install -y curl wget unzip procps RUN wget -P /root/ https://github.com/XTLS/Xray-core/releases/download/v24.11.5/Xray-linux-64.zip RUN unzip /root/Xray-linux-64.zip -d /root/ RUN chmod +x /root/xray COPY ./config.json /root/config.json RUN echo "#!/bin/sh\n/root/xray run -config /root/config.json\nsleep infinity" > /start.sh RUN chmod +x /start.sh RUN mkdir -p /var/log/xray RUN ln -sf ../usr/share/zoneinfo/Australia/Melbourne /etc/localtime EXPOSE 11451 CMD ["/start.sh" ]
配置文件 config.json
这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 { "log" : { "loglevel" : "warning" , "error" : "你的日志地址,例如/var/log/xray/error.log" , "access" : "你的日志地址,例如/var/log/xray/access.log" } , "inbounds" : [ { "listen" : "127.0.0.1" , "port" : 你的自定义的xray本地监听端口,例如11451 , "protocol" : "trojan" , "settings" : { "clients" : [ { "password" : "你自己想一个密码" } ] } , "streamSettings" : { "network" : "ws" , "wsSettings" : { "path" : "/自己设定的路径" } } , "sniffing" : { "enabled" : true , "destOverride" : [ "http" , "tls" ] } } ] , "routing" : { "rules" : [ { "type" : "field" , "protocol" : [ "bittorrent" ] , "outboundTag" : "blocked" } ] } , "outbounds" : [ { "sendThrough" : "0.0.0.0" , "protocol" : "freedom" , "settings" : { "redirect" : "127.0.0.1:WireGuard的本地监听端口" } } , { "tag" : "blocked" , "protocol" : "blackhole" , "settings" : { } } ] }
构建运行,这里直接让容器使用本地网络:
1 2 $ docker build -t wg-xray . $ docker run -d --name=wg-xray --network=host wg-xray
内网客户端配置 1️⃣ 配置 WireGuard 和前面基本一致,其他不再赘叙,不过配置文件修改如下:
1 2 3 4 5 6 7 8 9 [Interface] PrivateKey = 客户端生成的私钥Address = 上面配置的客户端地址MTU = 1280 [Peer] PublicKey = 服务端的公钥Endpoint = 127.0 .0.1 :xray监听的本地端口AllowedIPs = 服务端网段,例如 10.0 .0.0 /24
2️⃣ 配置 xray 直接上 Docker:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 FROM debian:latestARG DEBIAN_FRONTEND=noninteractiveRUN apt-get update && apt-get upgrade -y RUN apt-get install -y curl wget unzip procps RUN wget -P /root/ https://github.com/XTLS/Xray-core/releases/download/v24.11.5/Xray-linux-64.zip RUN unzip /root/Xray-linux-64.zip -d /root/ RUN chmod +x /root/xray COPY ./config.json /root/config.json RUN echo "#!/bin/sh\n/root/xray run -config /root/config.json\nsleep infinity" > /start.sh RUN chmod +x /start.sh RUN mkdir -p /var/log/xray RUN ln -sf ../usr/share/zoneinfo/Australia/Melbourne /etc/localtime CMD ["/start.sh" ]
配置文件 config.json
这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 { "log" : { "access" : "" , "error" : "" , "loglevel" : "warning" } , "inbounds" : [ { "tag" : "wireguard" , "port" : 本地代理端口例如11451 , "listen" : "127.0.0.1" , "protocol" : "dokodemo-door" , "settings" : { "address" : "127.0.0.1" , "port" : 本地代理端口例如11451 , "network" : "udp" , "followRedirect" : false } } ] , "outbounds" : [ { "tag" : "proxy" , "protocol" : "trojan" , "settings" : { "servers" : [ { "address" : "你的域名" , "method" : "chacha20" , "ota" : false , "password" : "你的密码" , "port" : 443 , "level" : 1 } ] } , "streamSettings" : { "network" : "ws" , "security" : "tls" , "tlsSettings" : { "allowInsecure" : false , "serverName" : "你的域名" , "fingerprint" : "chrome" } , "wsSettings" : { "path" : "/你的子路径" , "headers" : { "Host" : "你的域名" } } } , "mux" : { "enabled" : false , "concurrency" : -1 } } , { "tag" : "direct" , "protocol" : "freedom" , "settings" : { } } , { "tag" : "block" , "protocol" : "blackhole" , "settings" : { "response" : { "type" : "http" } } } ] , "dns" : { "hosts" : { "dns.google" : "8.8.8.8" , "proxy.example.com" : "127.0.0.1" } , "servers" : [ { "address" : "223.5.5.5" , "domains" : [ "你的域名" ] } , { "address" : "223.5.5.5" , "domains" : [ "geosite:cn" , "geosite:geolocation-cn" ] , "expectIPs" : [ "geoip:cn" ] } , "1.1.1.1" , "8.8.8.8" , "https://dns.google/dns-query" ] } , "routing" : { "domainStrategy" : "AsIs" , "rules" : [ { "type" : "field" , "inboundTag" : [ "api" ] , "outboundTag" : "api" } , { "type" : "field" , "port" : "443" , "network" : "udp" , "outboundTag" : "block" } , { "type" : "field" , "port" : "0-65535" , "outboundTag" : "proxy" } ] } }
构建运行,这里直接让容器使用本地网络:
1 2 $ docker build -t wg-xray . $ docker run -d --name=wg-xray --network=host wg-xray
成功连接,唯一的问题是延迟可能会相对比较高:
0x05. 可能遇到的问题 客户端能握手但无法连接其他终端,wg show 显示 allowed ips: (none)
在多设备连接到同一网络时可能出现,客户端无法连接到包括服务端在内的其他终端, 但是可能会有一个奇异现象是仅有一个客户端能正常连接 :
出现这种情况的原因可能是因为 IP 冲突 ,大概原因是客户端之间的 IP 相互 overlap 了,从而只有其中一个能够正常工作
最简单的解决方案是 把客户端的子网掩码 设为 /32 ,下面是一个示例配置文件:
1 2 3 4 5 6 7 8 9 10 [Interface] PrivateKey = 客户端私钥Address = 10.191 .98.10 /32 MTU = 1280 [Peer] PublicKey = 服务端公钥Endpoint = 服务端公网IP或域名:服务端配置的端口AllowedIPs = 10.191 .98.0 /24 PersistentKeepalive = 25
对应的服务端配置:
1 2 3 4 5 6 7 8 9 10 [Interface] Address = 10.191 .98.1 /24 PostUp = iptables -A FORWARD -i wg0 -j ACCEPTPreDown = iptables -D FORWARD -i wg0 -j ACCEPTListenPort = 服务端配置的端口PrivateKey = 服务端私钥[Peer] PublicKey = 客户端公钥AllowedIPs = 10.191 .98.10 /32
配置 WireGuard 后无法解析域名,但可以通过 IP 进行访问 可能是 DNS 解析出现问题,在客户端配置文件当中你可能会 习惯性 额外配置如下:
1 2 [Interface] DNS = 8.8 .8.8
这会覆盖原有 DNS 配置,但 DNS 服务器未必是可达的( 原因懂的都懂 ),最简单的解决方案是删除 DNS 配置或是更改成其他的可用的 DNS 服务器(例如 114.114.114.114
)