上一篇 下一篇 分享链接 返回 返回顶部

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

发布人:Minchunlin 发布时间:2025-09-25 09:38 阅读量:1129


凌晨 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 错误暴增

如果你也在远程办公的“悬崖边”挣扎,这套方案足够你一个通宵把业务拉回正轨;等天亮,你会像我一样,听见同事们说——“今天不卡了”。

目录结构
全文