我在香港把远程办公从“卡顿悬崖”拉回正轨:Ubuntu 20.04 + WireGuard VPN 叠加 CN2 专线的实战部署与深度优化

凌晨 2:40,我站在香港将军澳的数据中心机房,工单频道还在不断冒出“Zoom 语音断断续续”“Git 拉取龟速”的红点。白天的临时旁路把流量勉强压到了普通国际出口,可到了晚高峰就像在拥堵的跨海大桥上一脚油门一脚刹车。领导丢下一句:“今晚把远程办公稳定住,明早 9 点例会不能再卡。”
我决定把香港跳板从头拉一遍:用 Ubuntu 20.04 + WireGuard 做 VPN 叠加,强制把远程办公关键流量走 CN2 专线,并把链路、内核、队列和 MTU 一路打磨到底。
目标与拓扑
目标:让内地分支/居家员工稳定访问公司在香港与全球云上的服务(Git、Jira、Confluence、SaaS、VoIP/Zoom),降低丢包与抖动,保证会议与开发体验。
方案:香港机房部署一台 Ubuntu 20.04 服务器(以下简称 HK-GW),以 WireGuard 建立加密隧道;所有 VPN 流量强制从 CN2 专线口出站,其它后台管理/监控仍可走普通国际口。
拓扑(双上行):
中国内地员工/分支 ——(公网/宽带)——> HK-GW[wg0]
|—— eth0: 普通国际 BGP 上行(默认路由)
└—— eth1: CN2 专线(策略路由仅承载 wg0 流量)
硬件与链路清单(实装)
| 项目 | 型号/参数 | 关键点 |
|---|---|---|
| 服务器 | 1U / Xeon Silver 4210(10C)/ 64GB RAM / 2×1.92TB NVMe | NVMe 做日志与监控缓存,避免 IO 抖动影响隧道 |
| 网卡 | 2×10GbE (Intel X710) | 分别连接普通上行和 CN2 上行 |
| 系统 | Ubuntu Server 20.04.6 LTS(HWE 内核) | 内核自带 WireGuard 模块 |
| 上行1(eth0) | 普通国际 BGP 1Gbps | 作为默认路由,承载运维、管理与非关键流量 |
| 上行2(eth1 / CN2) | CN2 专线 500Mbps 对等,香港 PoP 直连 | 仅承载 wg0 隧道与办公关键流量 |
| 机柜配套 | 两路电、独立 PDU、远程 KVM | 夜间维护自救能力 |
基线测试(问题复现)
部署前,我抓了 3 组关键指标(内地↔香港):
| 场景 | RTT均值 | 抖动(Jitter) | 丢包 | TCP 吞吐 | UDP 实测 |
|---|---|---|---|---|---|
| 直连普通国际口 | 48–65ms | 8–25ms | 1–3% | 120–220 Mbps | 50–120 Mbps 不稳 |
| WireGuard over 普通口 | 50–70ms | 10–28ms | 1–3% | 100–180 Mbps | 40–100 Mbps 不稳 |
| WireGuard over CN2(预跑) | 28–34ms | 1–4ms | 0–0.2% | 300–480 Mbps | 300–450 Mbps 稳定 |
工具:mtr、iperf3 -u、ping -i 0.2 -M do -s <size>;样本取 5 分钟窗口,避开运营商突发调度。
部署步骤(一步不落)
1)系统准备与内核/时间
# 更新系统与时间同步
sudo apt update && sudo apt -y full-upgrade
sudo apt -y install chrony ethtool iproute2 net-tools
sudo timedatectl set-timezone Asia/Hong_Kong
sudo systemctl enable --now chrony
# 确认内核已带 WireGuard(20.04 HWE 通常 OK)
modinfo wireguard || sudo apt -y install wireguard-dkms wireguard-tools
2)安装 WireGuard 与密钥
sudo apt -y install wireguard
umask 077
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
SERVER_PRIV=$(cat /etc/wireguard/server_private.key)
SERVER_PUB=$(cat /etc/wireguard/server_public.key)
3)分配地址与网段规划
- 隧道网段(wg0):10.20.0.0/16
- 服务器地址:10.20.0.1/24
- 移动端/居家客户端:10.20.0.10/32 起
- 分支站点:10.30.10.0/24(示例)
避免与内地分支 LAN 冲突;若已有 10.0/8 大网,建议抽出单独 /16。
4)HK-GW(服务器)配置
/etc/wireguard/wg0.conf:
[Interface]
Address = 10.20.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIV>
# MTU 会在优化章节讲如何测;先保守 1420 或 1380
MTU = 1420
# 开启后置 NAT/安全策略,注意 nft/iptables 二选一,我这里用 iptables
PostUp = iptables -t nat -A POSTROUTING -o eth1 -s 10.20.0.0/16 -j MASQUERADE; \
iptables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x66; \
iptables -t mangle -A OUTPUT -o wg0 -j MARK --set-mark 0x66; \
sysctl -w net.ipv4.ip_forward=1
PostDown = iptables -t nat -D POSTROUTING -o eth1 -s 10.20.0.0/16 -j MASQUERADE; \
iptables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x66; \
iptables -t mangle -D OUTPUT -o wg0 -j MARK --set-mark 0x66
# 示例:先不写 Peer,待生成后追加
解释:
MASQUERADE:让 VPN 客户端访问公网时以 CN2 出口地址出站。
fwmark=0x66:把来自/发往 wg0 的包打标,稍后用策略路由让它们只走 CN2(eth1)。
5)策略路由:让 wg0 流量强制走 CN2
# 新建策略路由表
echo "200 cn2" | sudo tee -a /etc/iproute2/rt_tables
# 在 cn2 表中放一条默认路由(替换为你的 CN2 网关与接口)
sudo ip route add default via <CN2_GATEWAY_IP> dev eth1 table cn2
# 根据 fwmark 走 cn2 表
sudo ip rule add fwmark 0x66 lookup cn2
# 关闭严格反向路径检查,避免双线导致丢包
sudo sysctl -w net.ipv4.conf.all.rp_filter=2
sudo sysctl -w net.ipv4.conf.default.rp_filter=2
# 永久化(/etc/sysctl.d/99-sysctl.conf)
cat <<'EOF' | sudo tee /etc/sysctl.d/99-vpn-sysctl.conf
net.ipv4.ip_forward=1
net.ipv4.conf.all.rp_filter=2
net.ipv4.conf.default.rp_filter=2
# 提升 UDP 缓冲
net.core.rmem_max=2500000
net.core.wmem_max=2500000
net.core.rmem_default=1048576
net.core.wmem_default=1048576
# 对队列与拥塞控制友好
net.core.default_qdisc=fq
EOF
sudo sysctl --system
这样,wg0 的所有流量都只会从 eth1(CN2)出站;而服务器自身 SSH/apt 等默认仍走 eth0。
6)队列调度与网卡细节
# 对 wg0 用 fq_codel,控制队列抖动(需要 sch_fq_codel 模块)
sudo modprobe sch_fq_codel
sudo tc qdisc add dev wg0 root fq_codel
# 如 eth1 是 1G/500M,适当调 NIC ring buffer(示例 X710)
sudo ethtool -g eth1
sudo ethtool -G eth1 rx 4096 tx 4096
# 关闭易致抖动的分片/分片合并(根据网卡适配)
sudo ethtool -K eth1 gso off gro off tso off
7)生成客户端(移动端/PC)
以一个移动端/PC 客户端为例:
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
CLIENT1_PRIV=$(cat client1_private.key)
CLIENT1_PUB=$(cat client1_public.key)
把客户端 Peer 加入服务器 wg0.conf:
[Peer]
# client1
PublicKey = <CLIENT1_PUB>
AllowedIPs = 10.20.0.10/32
PersistentKeepalive = 25
客户端配置(client1.conf,导入 WireGuard App 或 wg-quick):
[Interface]
Address = 10.20.0.10/32
PrivateKey = <CLIENT1_PRIV>
DNS = 10.20.0.1
[Peer]
PublicKey = <SERVER_PUB>
Endpoint = <HK_GW_PUBLIC_IP>:51820
# 全隧道办公:需要外网也走 CN2,可设 0.0.0.0/0
# 若只访问公司网与特定 SaaS,可按需分流
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
说明:
Keepalive=25 能穿越大多数家庭 NAT。
如需站点到站点,把分支 LAN(如 10.30.10.0/24)写入双方 AllowedIPs,并在 HK-GW 做相应静态路由或转发。
8)开机自启与状态
sudo systemctl enable --now wg-quick@wg0
wg show
MTU 与分片:别拍脑袋,测!
在跨境与专线并存场景,MTU 最容易被忽视。我遵循“先测后定”的原则:
# 从客户端测试到香港 wg0(以 1380 为起点)
ping -c 5 -M do -s 1380 <HK_GW_PUBLIC_IP>
# 逐步加到不丢包的最大值(数据长度 + 28 字节 = 实际 IP 包)
经验值:CN2 + WireGuard 通常 1380–1420 比较稳。我最后定在 MTU=1412(考虑运营商偶发路径变化留余量)。
设置处:wg0.conf 的 MTU 字段 + 客户端同样设置;必要时对 eth1 也下调以避免底层分片。
专线分流(灵活版)
有些业务(如大文件 CDN、公开拉取容器镜像)不需要走 CN2,反而会占用宝贵的专线带宽。可以用 ipset + iptables mangle 做精细分流:
# 例:把 Git、Jira、Zoom、Office 365 的 IP 前缀做成 ipset(需自行维护)
sudo ipset create corp_saas hash:net
sudo ipset add corp_saas 13.107.6.0/24 # O365 示例段
sudo ipset add corp_saas 52.113.0.0/20 # Teams/Zoom 示例段(示意)
# 来自 wg0 且目的在 corp_saas 的流量才打 CN2 标记
sudo iptables -t mangle -A PREROUTING -i wg0 -m set --match-set corp_saas dst -j MARK --set-mark 0x66
# 其它 wg0 流量走默认(如需)
sudo iptables -t mangle -A PREROUTING -i wg0 -m set ! --match-set corp_saas dst -j MARK --set-mark 0x0
维护 ipset 的脚本可每日同步运营商/厂商前缀;别用域名直分流(DNS→IP 不稳定)。
安全加固(必要但不啰嗦)
# 最小暴露面:仅放行 51820/UDP 和 SSH 白名单
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -s <office_ip/32> -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -P INPUT DROP
# 持久化
sudo apt -y install netfilter-persistent
sudo netfilter-persistent save
监控:wg show, journalctl -u wg-quick@wg0, nstat -a, mtr -uwz。
密钥轮换:季度轮换一次 Peer 公私钥,老新并存 24 小时平滑迁移。
站点到站点(分支路由例)
分支(内地)若是软路由/小 Linux 盒子,同样装 wireguard-tools,配置大同小异:
分支端(Branch):
[Interface]
Address = 10.20.0.20/32
PrivateKey = <BR_PRIV>
[Peer]
PublicKey = <SERVER_PUB>
Endpoint = <HK_GW_PUBLIC_IP>:51820
AllowedIPs = 10.20.0.0/16, 10.10.0.0/16, 172.16.0.0/12 # 公司内网段
PersistentKeepalive = 25
HK-GW 增加分支 Peer:
[Peer]
# Branch
PublicKey = <BR_PUB>
AllowedIPs = 10.20.0.20/32, 10.30.10.0/24
并在分支路由器上添加把本地 LAN(如 10.30.10.0/24)出口指向本机(基于策略路由或静态路由)。
一键化脚本(上线当晚的“救火”版本,精简但可用)
注意:请根据你环境替换 <CN2_GATEWAY_IP>、接口名与密钥。
#!/usr/bin/env bash
set -euo pipefail
apt update && apt -y install wireguard iproute2 netfilter-persistent ipset ethtool
# sysctl
cat >/etc/sysctl.d/99-vpn-sysctl.conf <<'EOF'
net.ipv4.ip_forward=1
net.ipv4.conf.all.rp_filter=2
net.ipv4.conf.default.rp_filter=2
net.core.rmem_max=2500000
net.core.wmem_max=2500000
net.core.rmem_default=1048576
net.core.wmem_default=1048576
net.core.default_qdisc=fq
EOF
sysctl --system
# keys
umask 077
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
SERVER_PRIV=$(cat /etc/wireguard/server_private.key)
# wg0
cat >/etc/wireguard/wg0.conf <<EOF
[Interface]
Address = 10.20.0.1/24
ListenPort = 51820
PrivateKey = ${SERVER_PRIV}
MTU = 1412
PostUp = iptables -t nat -A POSTROUTING -o eth1 -s 10.20.0.0/16 -j MASQUERADE; \
iptables -t mangle -A PREROUTING -i wg0 -j MARK --set-mark 0x66; \
iptables -t mangle -A OUTPUT -o wg0 -j MARK --set-mark 0x66; \
sysctl -w net.ipv4.ip_forward=1
PostDown = iptables -t nat -D POSTROUTING -o eth1 -s 10.20.0.0/16 -j MASQUERADE; \
iptables -t mangle -D PREROUTING -i wg0 -j MARK --set-mark 0x66; \
iptables -t mangle -D OUTPUT -o wg0 -j MARK --set-mark 0x66
EOF
# policy route
grep -q "cn2" /etc/iproute2/rt_tables || echo "200 cn2" >> /etc/iproute2/rt_tables
ip route add default via <CN2_GATEWAY_IP> dev eth1 table cn2 || true
ip rule add fwmark 0x66 lookup cn2 || true
systemctl enable --now wg-quick@wg0
netfilter-persistent save
echo "Done. Add peers with: wg set wg0 peer <PUB> allowed-ips <CIDR>"
调优要点汇总(现场笔记)
MTU:用 ping -M do -s 实测,不偷懒。1412–1420 常见稳点。
Keepalive=25:穿 NAT 的通行证。
策略路由:用 fwmark + ip rule,把 wg0 流量锁死走 CN2,避免不稳的国际口“抢道”。
队列:fq_codel 压抖动;ethtool -K 适度关 GRO/TSO 防止合并导致延迟尾巴。
UDP 缓冲:增大 rmem/wmem,对高 RTT × 带宽产品有帮助。
监控:wg show 的 transfer, latest handshake 要盯;mtr 周期巡检。
分流:贵的走 CN2,便宜的走普通口,ipset 管理前缀,不要依赖 DNS 名称分流。
日志:NVMe 放 journald 永久目录与 mtr 报表,查故障快。
常见坑与解法(我当晚就踩了 3 个)
| 坑点 | 现象 | 根因 | 现场解法 |
|---|---|---|---|
| UFW 与 iptables 冲突 | 放行规则“看着有”,但连接被拒 | UFW/nft 与手写 iptables 混用 | 统一使用 iptables + netfilter-persistent,禁用 UFW |
| 子网重叠 | 某分支能连 VPN,但访问公司内网“绕自己圈” | 分支 LAN 与隧道网段冲突 | 改分支网段或在分支做 SNAT(权宜,后续整改) |
| rp_filter 导致回程黑洞 | 某些流量单向通 | 多上行时严格 RPF 丢包 | 设置 rp_filter=2(loose),策略路由匹配后恢复 |
上线后的指标对比(一周观察)
| 指标 | 优化前(高峰) | 优化后(CN2+WG) |
|---|---|---|
| Zoom/Teams 掉话率 | 3–6% | <0.3% |
| Git Clone 平均速率 | 3–8 MB/s | 25–45 MB/s |
| Jira 页面 P95 首次字节 | 1.8–3.2 s | 400–700 ms |
| 平均 RTT(内地↔HK) | 55 ms | 31 ms |
| 抖动 P95 | 22 ms | 3 ms |
清晨 5:10 的回铃
机房灯还那么亮。我把最后一条 ipset 更新任务丢进 cron,wg show 的握手时间稳稳落在 20 秒内滚动。到了 5 点多,华北的同事先上线,Slack 弹出一句“今天语音很顺”。
我靠在冷通道边的机柜,喝完一夜最后一口冰咖啡,心里只有一个结论:远程办公的稳定,不是“加条专线”这么简单。必须让数据路径与内核行为对齐业务诉求。Ubuntu 20.04 + WireGuard + CN2 专线,不是魔法,但只要细节盯住,从 MTU 到队列、从策略路由到分流清单,每一刀都精准落下,用户的“卡顿”就会从抱怨里消失。
附:运维日常检查清单(可直接抄走)
- wg show:latest handshake < 30s、无异常断链
- mtr -uwz 到核心 SaaS:丢包 <0.2%,抖动 <5ms
- tc -s qdisc show dev wg0:队列长度与丢包为零或可接受
- ip rule/ip route show table cn2:策略未被他人脚本覆盖
- iptables-save:mangle 标记与 NAT 仍在
- ipset list corp_saas:前缀更新成功
- journalctl -u wg-quick@wg0 --since "1 hour ago":无异常重启
- ethtool -S eth1:专线口无 FCS/CRC 错误暴增
如果你也在远程办公的“悬崖边”挣扎,这套方案足够你一个通宵把业务拉回正轨;等天亮,你会像我一样,听见同事们说——“今天不卡了”。