如何将两台VPS连接到私有网络
了解连接两台VPS的三种方案:服务商VPC、WireGuard和Tailscale。涵盖子网冲突、服务绑定、5432端口防火墙、MTU及重启后连接问题。
可以将两台 VPS 连接到私有网络吗?
可以。两台 VPS 可以加入同一个私有网络,每台机器都通过私有地址访问另一台机器,公网无法路由这些地址。不要走捷径:将 PostgreSQL 绑定到 0.0.0.0,在防火墙中开放端口 5432,然后仅依赖密码进行信任。公网 IP 上的数据库端口开放后,通常几小时内就会被扫描器发现。之后,一组凭据就成了数据与互联网之间唯一的防线。
构建私有连接有三种方式。具体选择取决于两台机器所在的位置。
- 使用服务商的私有网络或 VPC(虚拟私有云)。免费且速度快。仅当两台 VPS 位于同一服务商时可用,而且通常还必须位于同一区域。
- 自行配置 WireGuard 隧道。 提供加密连接,适用于任何能够相互访问的两台机器,每端大约需要配置十行。
- 使用 Tailscale 等托管式网状网络,或自行托管 Headscale。 当两台机器位于不同服务商、其中一台没有公网 IP,或预计稍后要添加第三台机器时,应选择此方式。
下文将介绍每种方式的配置方法,随后说明配置完成后可能导致连接故障的部分:子网选择、服务绑定、防火墙规则、MTU(最大传输单元),以及重启后的行为。
选择隧道子网后再开始配置
私有地址段一旦写入两台机器上的配置文件、连接字符串和防火墙规则,后续更改会很麻烦。因此,应选择不会与任一机器现有网络冲突的地址段。
地址冲突不会显示错误。内核会按最长前缀匹配进行路由,因此冲突通常是静默发生的:如果隧道声明使用 172.17.0.0/24,而 Docker 默认网桥已在该主机上占用 172.17.0.0/16,那么对于 172.17.0.x 中的每个地址,隧道的 /24 都会优先匹配。隧道仍然可以正常工作,但主机发往这些地址上容器的流量会改走隧道,导致容器停止响应。在两台机器上运行 ip route show,然后选择一个两个输出中都未出现的地址段。
应避免使用以下地址段:192.168.0.0/24 和 192.168.1.0/24,家庭及办公路由器通常会分配这些地址;172.17.0.0/16,即 Docker 默认网桥,以及 Docker Compose 为每个项目创建的 172.18 及更高网络;100.64.0.0/10,Tailscale 使用该地址段;以及云服务商为其私有网络分配的地址段。对于小型部署,10.90.0.0/24 是安全的选择,下面的示例也使用该地址段。
选项 1:提供商的私有网络或 VPC
大多数提供商都可以在私有地址范围内附加第二个网络接口,通常称为 VPC(虚拟私有云)。这不会产生额外费用,流量不会离开提供商自己的网络,因此往返延迟较低,但不会加密流量。需要满足两个条件:两台 VPS 必须由同一提供商提供,而且在大多数提供商处还必须位于同一区域。
该接口通常会在客户机中显示,但没有配置地址。您需要为它配置控制面板分配的地址。先查找该接口。
ip -brief address show
ip route show default私有 NIC(网络接口卡)是没有地址和默认路由的接口,名称通常类似 enp7s0 或 ens7。不要假定接口名称一定是 eth0。为该接口创建 netplan 文件,如 /etc/netplan/60-private.yaml:
network:
version: 2
ethernets:
enp7s0:
addresses:
- 10.20.30.11/24sudo chmod 600 /etc/netplan/60-private.yaml
sudo netplan apply
ip -brief address show enp7s0
ping -c 3 10.20.30.12现在,该接口应显示为 UP,并使用您设置的地址。在两台机器位于同一区域时,发送到另一台机器私有地址的 ping 应在远低于 1 毫秒内收到响应。可以忽略 chmod 和 netplan apply 关于文件权限过于开放的警告,因为 netplan 文件可能包含 Wi-Fi 密钥等机密信息。
它可以防护什么。 公网。私有地址无法从外部路由访问,因此扫描器根本看不到该端口。它无法防护什么。 提供商网络内部已有的任何主体。具体范围因提供商而异,请阅读提供商的文档:有些提供商只允许您自己的账户访问私有地址,另一些则允许该数据中心中的所有机器访问。流量也是明文传输,因此任何能够在该网段捕获数据包的主体都可以读取数据库行。当数据重要时,请在私有接口上运行选项 2:将 WireGuard 的 Endpoint 指向私有地址,即可在这条短路径上获得加密保护。
选项 2:在两台 VPS 之间建立 WireGuard 隧道
WireGuard 是内核中的三层隧道,自 Linux 5.6 起已进入主线内核,因此 Ubuntu 24.04 和 Debian 13 无需额外模块。在两台服务器之间,它是一条点到点链路:两端各自只配置一个对等端,无需设置 NAT(网络地址转换)或 IP 转发。对等端由公钥和该密钥可以使用的地址组成,因此 WireGuard 仅凭密钥即可同时决定路由和访问控制。
假设主机 A 是 Web 服务器,公网 IP 为 203.0.113.10;主机 B 是数据库服务器,公网 IP 为 198.51.100.20。主机 A 在隧道内使用 10.90.0.1,主机 B 使用 10.90.0.2。在两台机器上都运行:
sudo apt update && sudo apt install -y wireguard
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/host.key'
sudo sh -c 'wg pubkey < /etc/wireguard/host.key > /etc/wireguard/host.pub'
sudo chmod 600 /etc/wireguard/host.key
sudo cat /etc/wireguard/host.pub最后一行会输出公钥,将该公钥粘贴到另一台机器的配置中。.key 文件不会离开生成它的机器。请显式设置文件模式,因为仅使用 sudo 和 umask 077 不可靠:sudo 会将自身的 umask 应用到重定向创建的文件,而全局可读的私钥与没有隧道没有区别。
在主机 A 上写入 /etc/wireguard/wg0.conf:
[Interface]
Address = 10.90.0.1/24
ListenPort = 51820
PrivateKey = <host A private key>
[Peer]
PublicKey = <host B public key>
AllowedIPs = 10.90.0.2/32
Endpoint = 198.51.100.20:51820
PersistentKeepalive = 25主机 B:完整的 /etc/wireguard/wg0.conf
[Interface]
Address = 10.90.0.2/24
ListenPort = 51820
PrivateKey = <host B private key>
[Peer]
PublicKey = <host A public key>
AllowedIPs = 10.90.0.1/32
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 25两个文件都不包含另一台机器的私钥。每一端保留自己的私钥,以及另一端的公钥。使用 sudo cat /etc/wireguard/host.pub 复制公钥,并将 .key 文件保留在原位置。
/24 在 Address 中为整个隧道子网设置路由,因此以后可以加入第三台机器。AllowedIPs 是两端各自唯一的 /32,也就是另一台机器拥有的隧道地址。入站时,该列表充当访问控制列表:解密后数据包的源地址如果不在对等端的 AllowedIPs 中,就会被丢弃。因此,对等端无法冒充网络中的其他地址。这里两端都配置 Endpoint,因为两端都有公网 IP。这一点在重启后很重要:任一机器都可以发起握手,因此链路无需等待另一端发现变化即可恢复。更宽泛的 AllowedIPs、NAT 和 DNS 规则位于单服务器 WireGuard VPN 配置中,两台主机之间的链路不需要该指南中的路由器部分。
在两台机器上开放端口并启动接口:
sudo ufw allow 51820/udp comment 'wireguard'
sudo systemctl enable --now wg-quick@wg0
sudo wg show
ping -c 3 10.90.0.2wg show 应显示该对等端,并且两端都有几秒前的 latest handshake 和非零的 transfer。如果对等端没有握手记录,表示没有数据到达,或数据未被接受。检查提供商网络防火墙中的 UDP 51820。大多数控制面板中的该防火墙与 ufw 是独立的。然后检查密钥是否交叉配置:[Peer] 块必须包含另一台机器的公钥。enable --now 是该 systemctl 命令中重要的部分,因为手动运行的 wg-quick up wg0 会在下次重启后消失,而内核升级通常需要重启。
威胁模型。 WireGuard 会验证并加密每个数据包,因此无论经过哪条路径,流量都保持私密:包括你无法控制的提供商网络,或两个提供商之间的公网。对于已经被入侵的主机,WireGuard 无法提供保护,因为任一机器上的任何进程都可以访问隧道地址。
选项 3:为不同提供商的 VPS 使用托管网状网络
当两台机器位于不同提供商时,提供商的私有网络就不再适用。手动搭建 WireGuard 仍然可用。托管网状网络也可以满足需求。它使用相同的 WireGuard 数据平面,并增加一个协调服务器,用于分配地址、分发公钥,以及在一端没有公网 IP 时穿透 NAT 建立连接。
Tailscale 会为您运行该协调服务器。在两台机器上安装:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale upsudo tailscale up 会输出一个身份验证 URL。打开该 URL,登录后,机器就会加入您的 tailnet。然后在两端检查:
tailscale status
tailscale ip -4每台机器都会在名为 tailscale0 的接口上获得一个 100.64.0.0/10 地址,tailscale status 会列出另一台机器,并标明连接是直连还是经过中继。使用这些地址时,与使用 WireGuard 地址完全相同。如果另一台机器显示为中继连接,吞吐量通常会远低于线路速率:在 VPS 上,原因通常是 UDP 被阻断或存在严格 NAT;将中继的 Tailscale 连接改为直连介绍了这两种情况的处理方法。
服务器上有两项设置需要注意。节点密钥默认在 180 天后过期。密钥过期后,机器会立即从 tailnet 中移除,且当时不会发出警告。因此,请在管理控制台中为每台服务器启用 Disable Key Expiry。访问规则默认允许您自己的所有设备相互访问。对于两台服务器来说这没有问题,但笔记本电脑加入后就不安全了。对于无人值守的部署,请在控制台中创建身份验证密钥,并通过 sudo tailscale up --auth-key=tskey-auth-... 传递该密钥,这样机器无需执行浏览器登录步骤。
威胁模型。 两台机器之间的流量会进行端到端加密,协调服务器不会保存它们的私钥,因此无法读取数据库流量。但协调服务器会保存元数据,包括有哪些机器、它们使用哪些公钥,以及哪些机器可以相互通信。如果这无法接受,请自行运行控制平面。Headscale 是 Tailscale 控制服务器的开源实现,客户端仍使用官方客户端。如果之后需要访问某台机器后面的整个私有网段,而不只是访问该机器本身,则应从子网路由器发布路由。
将服务绑定到私有地址,而不是 0.0.0.0
如果服务仍在公网接口上响应,私有网络就失去了意义。先检查当前监听的地址:
sudo ss -tlnp0.0.0.0:5432 或 *:5432 表示端口在机器的所有地址上开放,包括公网地址。10.90.0.2:5432 表示端口只在隧道内部开放。对于 PostgreSQL,在 postgresql.conf 中设置:
listen_addresses = '10.90.0.2, 127.0.0.1'然后在 pg_hba.conf 中仅允许对端:
host appdb appuser 10.90.0.1/32 scram-sha-256重启服务,再次运行 sudo ss -tlnp,确认监听地址已更改。MySQL 的对应配置项是 bind-address = 10.90.0.2,配置文件为 /etc/mysql/mysql.conf.d/mysqld.cnf。Ubuntu 24.04 和 Debian 13 上的 MariaDB 不提供该文件,因此应将同一行写入 /etc/mysql/mariadb.conf.d/50-server.cnf。
容器最容易出现这个问题,因为 Docker 默认会在所有地址上发布端口,并写入 ufw 不会过滤的 iptables 规则。在端口映射中指定隧道地址:
services:
db:
image: postgres:17
ports:
- "10.90.0.2:5432:5432"使用普通的 "5432:5432" 时,数据库会绑定到公网 IP,而 ufw 仍会报告该端口被拒绝。这就是 即使 ufw 显示 deny,已发布的 Docker 端口仍可访问的原因。将已发布的端口绑定到隧道地址即可解决问题。如果两个服务运行在同一台主机上,应完全避免让数据库使用 ports:。
仅信任隧道的防火墙规则
所需规则应表示:仅允许此接口上的该端口,其他位置一律不允许。
sudo ufw allow in on wg0 to any port 5432 proto tcp comment 'db from tunnel'
sudo ufw status verboseufw status verbose 应显示仅允许 on wg0 上的 5432 端口,同时默认入站策略仍为 deny。接口规则比源地址规则更容易保持正确,因为 WireGuard 已验证到达 wg0 的数据包来自持有正确密钥的对等端。本配置所依赖的默认拒绝策略,详见VPS 的 ufw 防火墙基础。在 Tailscale 中,接口为 tailscale0,因此规则变为 sudo ufw allow in on tailscale0 to any port 5432 proto tcp。
提供商的私有网络没有可供信任的隧道,因此规则必须指定源地址:sudo ufw allow from 10.20.30.11 to any port 5432 proto tcp。这会信任任何能够在该网段上使用该源地址的设备。因此,单独使用选项 1 比另外两个选项弱。
从另一台机器测试路径:
sudo apt install -y netcat-openbsd
nc -vz 10.90.0.2 5432succeeded! 表示整个路径正常。Connection refused 表示服务未监听该地址,因此绑定地址不正确。连接停滞并最终超时,表示防火墙丢弃了数据包,因此规则不正确。根据这一差异,你可以确定下一步应打开哪个文件,从而无需同时修改两处。
封装链路上的 MTU:为什么大型查询会挂起
封装会增加字节开销。WireGuard 会在数据包外添加外层 IP 头、UDP 头、自身的 16 字节头部和 16 字节身份验证标签,因此隧道可承载的数据包小于底层链路可承载的数据包。MTU 设置过高时,现象往往难以判断:ping 正常工作,psql 可以建立连接,但某个返回大量结果的查询会永久挂起。小数据包可以通过,大数据包则会被丢弃;有时还不会返回 ICMP 消息来告知发送方原因。
此图表背后的数据
[
{
"label": "1500 path, IPv4 endpoint",
"path_mtu": 1500,
"wireguard_overhead": 60,
"tunnel_mtu": 1440
},
{
"label": "1500 path, IPv6 endpoint",
"path_mtu": 1500,
"wireguard_overhead": 80,
"tunnel_mtu": 1420
},
{
"label": "1450 path, IPv4 endpoint",
"path_mtu": 1450,
"wireguard_overhead": 60,
"tunnel_mtu": 1390
},
{
"label": "1400 path, IPv6 endpoint",
"path_mtu": 1400,
"wireguard_overhead": 80,
"tunnel_mtu": 1320
}
]这些数值是根据固定的每包开销计算得出的,不是测量结果。在端点使用 IPv4 的普通 1500 字节路径上,WireGuard 会增加 60 字节(外层 IP 头 20 字节、UDP 头 8 字节、WireGuard 头部 16 字节、身份验证标签 16 字节),因此可用于传输数据的空间为 1440 字节。端点使用 IPv6 时,外层头部增加 20 字节,可用空间降至 1420。这也是 wg-quick 将接口 MTU 设置为路由 MTU 减去 80 的原因:1420 对两种端点地址族都足够安全。如果提供商的网络还进行了额外封装,例如底层 VXLAN 网络的 MTU 为 1450,最大值会进一步降至第三行的 1390,在最后一行的 1400 字节路径上则降至 1320。
请通过测量确认,不要猜测。在主机 A 上执行:
ping -M do -s 1392 -c 3 10.90.0.2-M do 禁止分片,-s 设置有效负载大小,因此 1392 字节的有效负载加上 8 字节的 ICMP 头和 20 字节的 IP 头,正好是 1420 字节。收到回复表示 1420 可以正常工作。当大小超过接口允许的值时,ping 会立即失败,并返回本地错误,指出触发限制的 MTU:
ping: local error: message too long, mtu=1420如果在小于 1420 的某个大小处停止收到回复,请将成功的大小设置到两个配置的 [Interface] 部分,然后重启:
MTU = 1380Tailscale 会自行将 tailscale0 设置为 1280。这是任何 IPv6 路径必须承载的最小值,因此无需调整该选项。
隧道会让我的数据库变慢吗?
造成开销的不是加密。WireGuard 在内核中运行,在当前 VPS 的 CPU 上,这部分开销相对于网络传输很小。真正造成开销的是距离。每个查询都要等待一次完整的往返才能收到响应。因此,在 30 ms 链路上,一个页面依次执行 50 个小查询时,仅网络等待就需要 1.5 秒,与数据库本身的处理速度无关。
迁移前先测量两台主机。先在主机 B 上运行 iperf3 -s,然后从主机 A 运行:
ping -c 20 10.90.0.2
iperf3 -c 10.90.0.2让应用和数据库彼此靠近,位于同一区域,最好使用同一服务商的私有网络。跨服务商隧道适合延迟不重要的场景,例如应用服务器与备份服务器之间的连接:流量是在夜间进行的一次大规模传输,而不是白天成千上万次的小查询。
一端重启后会发生什么
重启会暴露手动配置时不会出现的启动顺序问题。
隧道不会恢复。 只有已启用的单元才会在启动时运行。systemctl is-enabled wg-quick@wg0 必须在两台机器上都输出 enabled。Tailscale 软件包会为您启用 tailscaled,因此安装后,systemctl is-enabled tailscaled 会输出 enabled。
服务在隧道建立前启动,因而无法绑定地址。 如果数据库被配置为监听 10.90.0.2,而 wg0 尚不存在,启动就会失败,因为此时机器还没有该地址。PostgreSQL 会在日志中明确说明原因:
LOG: could not bind IPv4 address "10.90.0.2": Cannot assign requested address
FATAL: could not create any TCP/IP sockets让服务在接口之后启动。先使用 systemctl list-units 'postgresql*' 确认单元名称,然后创建 drop-in 配置:
sudo install -d /etc/systemd/system/postgresql@17-main.service.d
sudo tee /etc/systemd/system/postgresql@17-main.service.d/after-wg.conf >/dev/null <<'EOF'
[Unit]
After=wg-quick@wg0.service
Wants=wg-quick@wg0.service
EOF
sudo systemctl daemon-reload另一种简单但不理想的做法是 net.ipv4.ip_nonlocal_bind = 1,它允许进程绑定机器当前并不拥有的地址。这样可以避免服务崩溃;在接口建立之前也不会有流量到达,因此应优先使用启动顺序配置。
应用不会重新连接。 远端重启时,隧道上的所有开放 TCP 连接都会断开。如果连接池从不检测连接,就会继续分发已失效的连接。应用随后会在首次查询时因 server closed the connection unexpectedly 或 connection reset by peer 失败,并且通常会一直失败,直到重启应用。请启用连接池的健康检查(SQLAlchemy 中使用 pool_pre_ping=True,其他系统则使用等效的借用时测试选项),不要假设链路永远不会中断。主机上的容器也需要同样处理:只有在 restart policy 这样配置时,compose 堆栈才会在重启后自动恢复。
FAQ
不同服务商的两台 VPS 可以通过私有网络连接吗?
可以,但不能使用某个服务商提供的私有网络,因为它只能连接该服务商内部的机器。在两台机器的公网 IP 之间建立 WireGuard,或使用 Tailscale 等托管式网状网络。两者都会加密网络流量;由于数据包会经过公网,这一点很重要。预计往返时间会受到两个数据中心之间距离的影响。将数据库迁移到远端之前,使用 ping 测量该延迟。
服务商提供的私有网络经过加密吗?
没有。它只会为你提供互联网无法路由的地址,不提供其他保护。其作用范围也因服务商而异:有些服务商只允许你的账户访问这些地址,另一些则允许同一数据中心内的所有机器访问。请查看服务商文档。如果数据敏感,应在私有接口上运行 WireGuard。这样可以保留较短的网络路径,同时增加加密保护。
应使用哪个私有子网,才能避免以后发生冲突?
选择一个两台机器当前都未使用的 /24,例如 10.90.0.0/24。避免使用 192.168.0.0/24 和 192.168.1.0/24,因为家庭和办公路由器会分配这些网段;也避免使用 172.17.0.0/16 及更大的网段,因为 Docker 会使用它们;不要使用 100.64.0.0/10,因为 Tailscale 会使用它;还要避开服务商为其私有网络分配的网段。在两台机器上运行 ip route show,选择一个两边输出中都未出现的网段。
为什么数据库拒绝通过隧道建立连接?
如果通过隧道执行 ping 成功,而 nc -vz 10.90.0.2 5432 返回 Connection refused,说明数据库没有监听隧道地址。运行 sudo ss -tlnp,读取端口旁边的地址:127.0.0.1:5432 不接受来自网络的连接。如果连接一直等待,最终超时,则是另一个问题:防火墙规则丢弃了数据包。请在 wg0 中添加允许该端口入站的规则。
两台 VPS 之间建立连接时,需要启用 IP 转发和 NAT 吗?
不需要。转发和伪装用于一台机器代表其他机器路由网络流量的情况。两台主机之间的连接只承载发往这两台主机自身的流量,因此保持 net.ipv4.ip_forward 为 0,不添加 NAT 规则。只有当一台机器需要访问另一台机器后方的其他网络时,才需要启用转发。