Web 与数据库分在两台 VPS?跨机房 MySQL 3306 安全连接:WireGuard 内网网格 vs TLS 加密

网页与数据库分开部署时,公网直开 3306 等于敞开拖库。用 WireGuard 加密内网或 MySQL 原生 TLS 双管齐下,附真实配置与跨机房延迟优化。

把网站拆成两层,是很多人在 VPS 上跑业务时的常见做法:Web 层(PHP、Node.js、Python 之类的应用)跑在一台机器上,MySQL 这种数据库单独扔在另一台机器上。这么干好处挺实在——Web 机可以选 CPU 和内存大一点、硬盘一般的配置;数据库机反过来,要的是内存大、磁盘 IO 强。有些朋友还会特意把数据库放在更便宜的大盘机器上,或者为了数据安全和备份方便单独隔离。

但问题也恰恰出在“分开”这件事上。两台机器之间要通信,数据库端口 3306 就得对 Web 机开放。偷懒的做法是在数据库那台 VPS 的防火墙里把 3306 端口对公网 0.0.0.0/0 打开,或者只限制了一个会变的源 IP。这等于把家里保险柜的门朝大街敞开,还贴了张纸条写“里面有金条”。

延伸阅读

更多相关攻略推荐:【TCP优化 02】为什么晚高峰 Ping 值正常,SSH 却卡到掉高级玩家玩出花:如何在 VPS 上广播自己的 IP 地址?BYOIP打死不宕机:Linux 系统内核免重启升级(Kexec 快速切换与 【VPS 进阶玩法精选 011】VPS 上 MySQL / PostAWS / GCP 的"出站流量税":云巨头按 GB 扣费解析与 B

公网直开 3306 到底有多危险

很多人觉得“我密码够强,开着也没事”,这是一个很天真的想法。公网暴露 3306 至少带来三层风险:

  • 暴力破解:3306 是黑客端口扫描的重点目标,常年被全球僵尸网络轮番爆破。弱口令一秒进,强口令也只是拖延时间,暴露面越大被盯上的概率越高。
  • 明文嗅探与中间人:默认情况下 MySQL 客户端和服务端之间的通信是明文的。只要有人能摸到你和数据库之间的任意一段网络(公开的 Wi-Fi、被污染的运营商链路、跨国骨干上的嗅探节点),你发的 SQL、查回来的用户数据、甚至账号密码,全都能被看得一清二楚。
  • 拖库与勒索:一旦被攻破,整个库被人打包下载(拖库),用户隐私、订单、密码哈希全泄露;更狠的直接把库加密勒索。对一个小站长来说,这基本等于社死加法律风险。

所以结论很明确:跨机房访问数据库,必须做两件事——把暴露面收起来、把传输加密。下面给两条实战路线,你可以只上一条,也可以双管齐下。

方案 A:用 WireGuard 搭一条跨机房加密内网

WireGuard 是现代的轻量级 VPN,跑在内核里,性能损耗极小(比老牌的 OpenVPN 快得多),配置也比 OpenVPN 简单一个数量级。它的核心思路是:在两台 VPS 之间建立一条加密隧道,各自分配一个私有虚拟 IP(比如 10.0.0.x),之后 Web 应用连数据库就用这个虚拟 IP,公网上根本看不到 3306。

假设 Web 机(VPS A)公网 IP 是 1.2.3.4,数据库机(VPS B)公网 IP 是 5.6.7.8。我们给 A 分配隧道 IP 10.0.0.1,给 B 分配 10.0.0.2,整个隧道用 10.0.0.0/24 这个网段。

先在两边都装好:

sudo apt update
sudo apt install -y wireguard wireguard-tools

然后各自生成一对密钥(公钥给别人,私钥自己留着):

wg genkey | tee /etc/wireguard/privatekey | wg pubkey | tee /etc/wireguard/publickey

数据库机(VPS B)的配置文件 /etc/wireguard/wg0.conf:

[Interface]
Address = 10.0.0.2/24
ListenPort = 51820
PrivateKey = 替换为B机的私钥

[Peer]
PublicKey = 替换为A机的公钥
AllowedIPs = 10.0.0.1/32
PersistentKeepalive = 25

Web 机(VPS A)的配置文件 /etc/wireguard/wg0.conf:

[Interface]
Address = 10.0.0.1/24
PrivateKey = 替换为A机的私钥

[Peer]
PublicKey = 替换为B机的公钥
Endpoint = 5.6.7.8:51820
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25

WireGuard 配置的几个要点

  • AllowedIPs 是本方案的灵魂:它决定“哪些目标 IP 的流量走这条隧道”。我们这里只把对端那一个虚拟 IP(10.0.0.2/32 或 10.0.0.1/32)放进 AllowedIPs,意思是“只有去对方的虚拟 IP 才走隧道”。千万不要图省事写成 0.0.0.0/0,那是“全量路由(full tunnel)”,会把 Web 机所有出口流量都绕到数据库机,整个网络就乱套了。点对点连数据库,点对点路由就够了。
  • Endpoint 只在有公网的一侧写:Web 机要主动连数据库机,所以 Web 机的 [Peer] 里写 Endpoint = 数据库机公网IP:51820;数据库机不需要写 Endpoint,因为它只被动等连接。如果两边都公网可达,两边都可以写 Endpoint,谁先发包谁先握手。
  • PersistentKeepalive = 25:当一端在 NAT 后面时,定期发保活包维持映射。跨机房两台都有公网 IP 其实可以省略,但加上无害,推荐留着。
  • 防火墙收窄:数据库机公网只需放通 UDP 51820,且最好限制来源为 Web 机公网 IP;更重要的是,MySQL 的 3306 只对隧道 IP 开放:ufw allow from 10.0.0.1 to any port 3306。这样公网扫描器根本摸不到 3306。

启动并设为开机自启:

sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
sudo wg show

验证一下,从 Web 机 ping 数据库机的隧道 IP:

ping 10.0.0.2

通了之后,你的网站配置文件里数据库地址从 5.6.7.8 改成 10.0.0.2 即可,所有 3306 流量都被 WireGuard 加密,外界看到的只是两台机器之间一段 UDP 51820 的乱码。

如果你嫌手动交换公钥、配 Endpoint 麻烦,还有一个更省心的选择:Tailscale。它底层就是 WireGuard,但帮你做好了密钥分发、自动打洞、魔法 DNS(直接用主机名互相访问)。小白友好,免费层够小站点用;缺点是把控制面交给第三方,且免费设备数有限。追求完全自主可控就自己搭 WireGuard。

方案 B:让 MySQL 自己走 TLS 加密

就算不架 VPN,也可以让 MySQL 自己的通信加密,这叫原生 TLS(SSL)。思路是给服务端发一张证书,客户端连的时候验证并加密。MySQL 8.0 第一次启动时就会在 data 目录自动生成自签名证书(ca.pem、server-cert.pem、server-key.pem),所以大概率你已经有了。

先确认 SSL 状态:

mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"

如果 have_ssl 是 YES 且证书路径都指向真实文件,说明已经就绪。想自己掌控证书(推荐,方便把 CA 分发给客户端做严格校验),可以这样生成:

# 生成 CA
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca.pem -subj "/CN=MySQL-CA"

# 生成服务端证书
openssl genrsa -out server-key.pem 4096
openssl req -new -key server-key.pem -out server-csr.pem -subj "/CN=mysql-server"
openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 3650

# 生成客户端证书(做双向认证时用到)
openssl genrsa -out client-key.pem 4096
openssl req -new -key client-key.pem -out client-csr.pem -subj "/CN=mysql-client"
openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 3650

把 ca.pem、server-cert.pem、server-key.pem 放到 data 目录(如 /var/lib/mysql/),权限收紧:

chown mysql:mysql /var/lib/mysql/server-key.pem
chmod 600 /var/lib/mysql/server-key.pem

在 my.cnf(或 /etc/mysql/mysql.conf.d/mysqld.cnf)的 [mysqld] 段加上:

[mysqld]
ssl_ca = /var/lib/mysql/ca.pem
ssl_cert = /var/lib/mysql/server-cert.pem
ssl_key = /var/lib/mysql/server-key.pem
require_secure_transport = ON

require_secure_transport = ON 是整件事的灵魂。不写它,SSL 只是“可用但非强制”,客户端照样能明文连,等于没穿衣服还以为自己穿了。加上之后,所有走 TCP 的连接都强制加密(本地 socket 连接不受影响,因为本就被认为是安全的)。改完重启:

sudo systemctl restart mysql

进一步,可以给某个应用账号强制加密,甚至要求客户端出示证书(双向 TLS,更狠):

ALTER USER 'app'@'%' REQUIRE SSL;
-- 想要双向认证,用下面这行代替上一行:
-- ALTER USER 'app'@'%' REQUIRE X509;

客户端这边连接时带上 CA 并校验身份:

mysql -u app -p --ssl-ca=ca.pem --ssl-mode=VERIFY_CA -h 数据库公网IP

ssl-mode 有几个档:DISABLED 完全不加密;PREFERRED 能加密就加密;REQUIRED 必须用加密;VERIFY_CA 还要校验服务端证书是否由你信任的 CA 签发;VERIFY_IDENTITY 进一步核对证书里的主机名。生产环境至少用 VERIFY_CA,否则别人伪造个证书照样能中间人。

连上后确认确实加密了:

mysql> SHOW STATUS LIKE 'Ssl_cipher';

只要 Ssl_cipher 不是空,就说明这条连接已经是加密的。

一个容易踩的坑:TLS 不解决“暴露面”问题

要分清两件事:WireGuard 解决的是“收窄暴露加加密”,MySQL TLS 解决的只是“加密”。开了 TLS,3306 端口依然在公网暴露,依然会被扫描、被爆破用户名。所以 TLS 一定要配合防火墙限制源 IP(只允许 Web 机公网 IP 访问 3306)一起用。换句话说,TLS 防的是“窃听”,VPN 防的是“摸到”。最稳妥是两者叠加:WireGuard 内网加 MySQL 再开 TLS,纵深防御。

性能损耗对比:加密本身不是瓶颈

很多人担心“加了密会不会慢”。实测下来,加密几乎不是瓶颈:

  • WireGuard:用 ChaCha20/Poly1305 或 AES-GCM,现代 CPU 大多带 AES-NI 指令集,加解密几乎零感知,吞吐能吃到裸带宽的九成以上,延迟增加通常不到 1 毫秒。
  • MySQL TLS:每个新连接要做一次 TLS 握手(1 到 2 个往返)。如果你用的是短连接、每次请求都新建连接,握手开销会累积;但只要用连接池复用连接,握手只在建池时发生一次,后续查询几乎无感。TLS 1.3 比 1.2 握手更快。CPU 开销同样靠 AES-NI 缓解,或者选支持 AES-NI 的机型。

真正的性能杀手:跨机房网络延迟

比起加密,跨机房的网络延迟才是数据库读写的大敌。几个实测量级:东亚到北美往返 200 到 300ms 很常见;欧美之间(比如法兰克福到纽约)也要 80 到 120ms;同大洲内(美东美西)大概 30 到 70ms。

数据库是“交互式”的:应用每发一条 SQL,都要等服务器回包才能发下一条(round-trip)。如果一条事务里有 10 条 SQL 且逐条串行,延迟就乘以 10 往上叠。有公开测试数据很说明问题:在网络延迟约 320ms 的环境下,500 条 INSERT 不批处理要 167 秒,批处理后只要 1.2 秒,快了一百多倍。阿里云一个跨域事务的案例也类似:没优化时一次跨域写事务要 270ms,通过“事务专用连接池加把多条 SQL 批量合并(JDBC 的 rewriteBatchedStatements)”,把应用与数据库的交互次数从 6 次压到 1 次,耗时直接降到 70ms。

跨机房延迟的优化手段

  • 批处理是王道:INSERT 用多值写法或批量接口;MySQL JDBC 加 rewriteBatchedStatements=true;PostgreSQL 用 COPY 或 executeBatch。把 N 次往返压成 1 次。
  • 连接池复用:用 HikariCP、ProxySQL 之类复用连接,避免每次新建连接的手握和鉴权开销。
  • 减少事务内往返:把多条 SQL 合并、用存储过程、用多语句一次发送,别在应用里一条条循环发。
  • 读写分离:把大量读请求分到靠近用户的只读副本,只有写请求才跨机房打主库(ProxySQL / MySQL Router 都能做)。
  • 应用层缓存:用 Redis 把热点数据放在应用侧,写操作异步回写,减少实时跨机房查询。
  • 能下推就下推:聚合、JOIN 尽量让数据库在本地算完,别把原始数据拉到应用再算,省的是传输量。

如果业务对延迟极敏感又必须跨大洲,最彻底的办法其实是把应用和数据库放同一个机房(最快),或者至少放在同一云厂商的同一区域多可用区,而不是真跨大洲。

给你的落地建议

总结一下:

  • 小站长最省心:WireGuard 点对点加防火墙限制源 IP,MySQL 甚至可以不动配置,因为 3306 根本不对外暴露。
  • 要更稳:WireGuard 内网之上,再给 MySQL 开 TLS,双保险。
  • 切记:3306 绝不能对 0.0.0.0/0 全开。
  • 跨机房慢,根因是延迟不是加密,靠批处理、连接池、缓存去化解。

常见问题 FAQ

问:Web 和数据库分两台 VPS 安全吗? 答:安全且更稳,但 3306 绝不能裸奔公网。用 WireGuard 把两台组内网,或给 MySQL 套 TLS 加密,只放行应用服务器 IP。这样即便跨机房,数据也不在公网明文传输。

问:WireGuard 和 TLS 怎么选? 答:WireGuard 是把两台连成虚拟内网,整体流量加密、配置简单,适合固定多服务互联;TLS 是单连接加密,适合对外暴露的端点。两者可叠加:WireGuard 内网 + TLS 加固。

问:跨机房延迟影响大吗? 答:同区域(如美西内)几毫秒可忽略;跨大洲几十到上百毫秒,会拖慢每次查询。把 Web 与 DB 放同机房最稳,必须跨区就用连接池、读副本与缓存(Redis)减往返。