通过 WireGuard 将云服务器公网IP映射到内网服务器

内网穿透 家宽附加公网IP


1. 场景说明

  • 云服务器(VPS):有公网 IP,作为流量入口
  • 内网服务器:没有公网 IP,但能连上网
  • 目标:外网客户端通过云服务器访问内网服务,保留真实客户端 IP

2. 流程图

image


3. 部署步骤

3.1 云服务器安装必需软件

apt update && apt install -y wireguard iproute2 iptables
  • wireguard:VPN 模块和工具(wg, wg-quick
  • iproute2:提供 ipip ruleip route 等路由工具
  • iptables:做 DNAT 转发和防火墙规则

3.2 生成 WireGuard 密钥

云服务器(服务端)

# 生成私钥,保存到文件
wg genkey | tee /etc/wireguard/server_private.key \
# 同时生成公钥
| wg pubkey > /etc/wireguard/server_public.key

# 设置私钥权限,避免被非 root 读取
chmod 600 /etc/wireguard/server_private.key

内网服务器(客户端)

wg genkey | tee /etc/wireguard/client_private.key \
| wg pubkey > /etc/wireguard/client_public.key
chmod 600 /etc/wireguard/client_private.key

为什么要两边都生成?
因为 WireGuard 是点对点加密,双方都要有自己的密钥对,并保存对方的公钥。


3.3 云服务器配置 /etc/wireguard/wg0.conf

cat > /etc/wireguard/wg0.conf <<'EOF'
[Interface]
# WireGuard 虚拟网卡 IP(服务端)
Address = 10.0.5.1/24

# 监听 UDP 端口(在云厂商安全组放行)
ListenPort = 60000

# 云服务器私钥(替换变量)
PrivateKey = <SERVER_PRIVATE_KEY>

# 启动时执行的命令:
# 1. 开启 IPv4 转发(允许转发数据包)
PostUp   = sysctl -w net.ipv4.ip_forward=1
# 2. 放宽 rp_filter,防止非对称路由丢包
PostUp   = sysctl -w net.ipv4.conf.all.rp_filter=2

# 保留 SSH & WireGuard 端口,不做 DNAT 转发(防止自己被转发走)
PostUp   = iptables -t nat -I PREROUTING -i eth0 -p tcp -d <CLOUD_PRIV_IP> --dport 22     -j RETURN
PostUp   = iptables -t nat -I PREROUTING -i eth0 -p udp -d <CLOUD_PRIV_IP> --dport 22     -j RETURN
PostUp   = iptables -t nat -I PREROUTING -i eth0 -p tcp -d <CLOUD_PRIV_IP> --dport 60000  -j RETURN
PostUp   = iptables -t nat -I PREROUTING -i eth0 -p udp -d <CLOUD_PRIV_IP> --dport 60000  -j RETURN

# 将其他流量 DNAT 到内网机 wg0 IP
PostUp   = iptables -t nat -A PREROUTING -d <CLOUD_PRIV_IP> -j DNAT --to-destination <CLIENT_WG_IP>

# 放行公网→wg0 转发
PostUp   = iptables -A FORWARD -i eth0 -o wg0 -d <CLIENT_WG_IP> -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# 放行 wg0→公网 转发
PostUp   = iptables -A FORWARD -i wg0  -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

# 停止时撤销规则(与 PostUp 一一对应)
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp -d <CLOUD_PRIV_IP> --dport 22     -j RETURN
PostDown = iptables -t nat -D PREROUTING -i eth0 -p udp -d <CLOUD_PRIV_IP> --dport 22     -j RETURN
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp -d <CLOUD_PRIV_IP> --dport 60000  -j RETURN
PostDown = iptables -t nat -D PREROUTING -i eth0 -p udp -d <CLOUD_PRIV_IP> --dport 60000  -j RETURN
PostDown = iptables -t nat -D PREROUTING -d <CLOUD_PRIV_IP> -j DNAT --to-destination <CLIENT_WG_IP>
PostDown = iptables -D FORWARD -i eth0 -o wg0 -d <CLIENT_WG_IP> -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -D FORWARD -i wg0  -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

[Peer]
# 客户端公钥
PublicKey = <CLIENT_PUBLIC_KEY>
# 允许客户端的 wg IP
AllowedIPs = <CLIENT_WG_IP>/32
EOF

3.4 内网服务器配置 /etc/wireguard/wg0.conf

cat > /etc/wireguard/wg0.conf <<'EOF'
[Interface]
# 客户端 wg IP
Address = 10.0.5.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>

# 不修改系统默认路由
Table = off

# 进入 wg0 的连接打标记,用于策略路由
PostUp   = iptables -t mangle -A PREROUTING -i wg0 -j CONNMARK --set-mark 0x1
# 回程包也打相同标记
PostUp   = iptables -t mangle -A OUTPUT -m connmark --mark 0x1 -j MARK --set-mark 0x1

# 策略路由:标记包走 table 100
PostUp   = ip rule add fwmark 0x1 lookup 100
# table 100 的默认路由走 wg0
PostUp   = ip route add default dev wg0 table 100

# 停止时撤销规则
PostDown = iptables -t mangle -D PREROUTING -i wg0 -j CONNMARK --set-mark 0x1
PostDown = iptables -t mangle -D OUTPUT -m connmark --mark 0x1 -j MARK --set-mark 0x1
PostDown = ip rule del fwmark 0x1 lookup 100
PostDown = ip route del default dev wg0 table 100

[Peer]
# 云服务器公钥
PublicKey = <SERVER_PUBLIC_KEY>
# 云服务器公网 IP + 端口
Endpoint = <CLOUD_PUBLIC_IP_OR_FQDN>:60000
# 只允许与云服务器 wg IP / 公网 IP 通信
AllowedIPs = 10.0.5.1/32, <CLOUD_PUBLIC_IP>/32
# 保活防止 NAT 断开
PersistentKeepalive = 25
EOF

3.5 启动与开机自启

云服务器和本地服务器分别执行:

# 设置开机启动
systemctl enable wg-quick@wg0
# 立即启动
systemctl start wg-quick@wg0
# 查看状态
wg

wg 输出中 latest handshake 不为空,说明隧道已连通。


3.6 测试访问

内网机

python3 -m http.server 8080

外网机

curl -I http://<云服务器公网IP>:8080

如果能访问且内网机日志中显示外网客户端真实 IP,则成功。

消息盒子

# 暂无消息 #

只显示最新10条未读和已读信息