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

什么教程太麻烦不想看 一键脚本了解下

内网穿透、家宽附加公网 IP、wg公网IP
本文手把手带你把云服务器的公网入口映射到内网服务器,并保证:

  • 云服务器做内网入口(不当内网机默认出口)
  • 可直达内网机本机服务 / Docker / 再 DNAT 到其他内网主机
  • 统一阻断:你在“其他防火墙”(UFW/Firewalld/自建 iptables)里的入站策略,经由 WG 也同样生效

适用场景与方案选型

场景:内网服务器(家宽/NAT 后)无公网 IP,但能主动连外网。你拥有一台云服务器(VPS)有公网入口,想把这个公网入口“加到”内网机/容器/其他内网主机上。

方案选型:采用 WireGuard 隧道 + 云侧 SNAT + 客户端 MASQUERADE

  • 云服务器上:对“发往内网机”的包做 SNAT→云的 WG IP,固定回程路径。
  • 内网机上:当把流量再转给局域网/容器时 MASQUERADE,确保回包先回内网机,再回 WG。
  • 不使用复杂策略路由;云只做入口,内网机默认出口照旧。
  • 你在内网机的 INPUT/DOCKER-USER/FORWARD 规则,就能统一控制所有入口流量。

环境与参数准备

下表是本文示例用到的参数。你可以按需替换为自己的(推荐把它们写成环境变量)。

名称 示例值 说明
CLOUD_PUBLIC_IP 47.93.59.203 云的公网入口(外部访问它)
CLOUD_PRIV_IP 172.24.8.24 云主机 eth0 私网地址(很多云商在网关做过 DNAT)
WG_NET 10.0.5.0/24 WireGuard 内网网段
WG_SERVER_IP 10.0.5.1 云服务器 wg0 地址
WG_CLIENT_IP 10.0.5.2 内网服务器 wg0 地址
WG_PORT 60000/udp WireGuard 监听端口
SERVER_PRIVATE_KEY `` 云侧 WG 私钥(不要泄露
SERVER_PUBLIC_KEY `` 云侧 WG 公钥
CLIENT_PRIVATE_KEY `` 内网侧 WG 私钥(不要泄露
CLIENT_PUBLIC_KEY `` 内网侧 WG 公钥

安装与准备工作

云服务器安装软件

apt update && apt install -y wireguard iproute2 iptables
  • wireguardwg/wg-quick 工具与内核模块
  • iproute2ip/ip rule/ip route 路由工具
  • iptables:做 DNAT/SNAT 与转发控制

云安全组务必放行:WG_PORT (60000/udp),以及你要映射给内网的业务端口(80/443/自定义)。

生成密钥(含 PSK)

1) 生成服务端/客户端密钥对

服务端(云):

wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
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

2) 生成预共享密钥(PSK)

任意一端执行

wg genpsk | tee /etc/wireguard/psk.key
chmod 600 /etc/wireguard/psk.key

psk.key 内容复制到两端配置的 [Peer] 段中:
PresharedKey = <PSK内容>
PSK 是可选但强烈推荐的第二道加密

## 配置云服务器(服务端)

> **要点**:
> 
> 1. PREROUTING:除 SSH/WG 自身端口外,**其余端口全部 DNAT → `10.0.5.2`**
> 2. FORWARD:放行 `eth0→wg0` 与回程 `wg0→eth0`
> 3. **POSTROUTING(关键)**:**SNAT 源地址为 `10.0.5.1`**,固定回程路径、避免“时好时坏”

**文件**:`/etc/wireguard/wg0.conf`

```ini
[Interface]
# 云服务器 wg0 地址(WireGuard 网段)
Address = 10.0.5.1/24

# WireGuard 监听端口(需在安全组放行)
ListenPort = 60000

# 云服务器私钥(内容本身,而非路径)
PrivateKey = <SERVER_PRIVATE_KEY>

# ======= 启动后系统/防火墙设置 =======

# 1) 开启内核转发
PostUp   = sysctl -w net.ipv4.ip_forward=1
# 2) 放宽 rp_filter,防止“非对称路由”被丢弃(更稳)
PostUp   = sysctl -w net.ipv4.conf.all.rp_filter=2
PostUp   = sysctl -w net.ipv4.conf.default.rp_filter=2
PostUp   = sysctl -w net.ipv4.conf.wg0.rp_filter=2
PostUp   = sysctl -w net.ipv4.conf.eth0.rp_filter=2

# 3) 保留 SSH/WG 端口,不做 DNAT(注意匹配云的私网入站 IP)
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

# 4) 其余到 <CLOUD_PRIV_IP> 的流量全部 DNAT 给内网机 wg IP
PostUp   = iptables -t nat -A PREROUTING -i eth0 -d <CLOUD_PRIV_IP> -j DNAT --to-destination <WG_CLIENT_IP>

# 5) 允许 eth0→wg0 正向转发 & wg0→eth0 回程
PostUp   = iptables -A FORWARD -i eth0 -o wg0 -d <WG_CLIENT_IP> -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostUp   = iptables -A FORWARD -i wg0  -o eth0               -m state --state ESTABLISHED,RELATED -j ACCEPT

# 6) **关键:云侧 SNAT(源改为 10.0.5.1)**
PostUp   = iptables -t nat -A POSTROUTING -o wg0 -d <WG_CLIENT_IP> -j SNAT --to-source <WG_SERVER_IP>

# ======= 停止时回滚 =======
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 -i eth0 -d <CLOUD_PRIV_IP> -j DNAT --to-destination <WG_CLIENT_IP>
PostDown = iptables -D FORWARD -i eth0 -o wg0 -d <WG_CLIENT_IP> -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -D FORWARD -i wg0  -o eth0               -m state --state ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o wg0 -d <WG_CLIENT_IP> -j SNAT --to-source <WG_SERVER_IP>

[Peer]
# 客户端(内网机)公钥
PublicKey = <CLIENT_PUBLIC_KEY>
PresharedKey = <PSK>
# 只允许客户端的 WG 地址
AllowedIPs = <WG_CLIENT_IP>/32

为什么匹配 <CLOUD_PRIV_IP> 很多云商(阿里/腾讯等)在公网入口处已经做过一次 DNAT,到你主机时的目的地址是私网 IP(eth0),不是公网 IP。
SNAT 的意义:把外部请求的源地址统一改成 10.0.5.1,内网侧回包就一定走 wg0 → 云 → 外网,路径唯一、稳定。


配置内网服务器(客户端)

要点

  • 不改变默认路由(云只做入口)
  • 放宽 rp_filter(稳定)
  • 当把 WG 流量再转给局域网/容器时 MASQUERADE,确保回包回到内网机
  • AllowedIPs 最小化:只和云的 wg0 & 公网 IP 通信,不劫持默认出口

文件/etc/wireguard/wg0.conf

[Interface]
# 内网机 wg0 地址
Address    = 10.0.5.2/24

# 内网机私钥
PrivateKey = <CLIENT_PRIVATE_KEY>

# 不改默认路由(云只做入口;内网机主动上网仍走本地出口)
Table      = off

# ======= 启动后系统/防火墙设置 =======

# 1) 开启内核转发 + 放宽 rp_filter(all/default/wg0/eth0 都放宽)
PostUp   = sysctl -w net.ipv4.ip_forward=1
PostUp   = sysctl -w net.ipv4.conf.all.rp_filter=2
PostUp   = sysctl -w net.ipv4.conf.default.rp_filter=2
PostUp   = sysctl -w net.ipv4.conf.wg0.rp_filter=2
PostUp   = sysctl -w net.ipv4.conf.eth0.rp_filter=2

# 2) 把从 WG 再转发到局域网/容器的流量做 MASQUERADE(保证回包回到内网机)
PostUp   = iptables -t nat -A POSTROUTING -s 10.0.5.0/24 -o eth0 -j MASQUERADE

# 3) 转发基础放行(更细的策略交给你的 UFW/Firewalld/DOCKER-USER)
PostUp   = iptables -A FORWARD -i wg0 -o eth0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostUp   = iptables -A FORWARD -i eth0 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT

# ======= 停止时回滚 =======
PostDown = iptables -t nat -D POSTROUTING -s 10.0.5.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o eth0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PostDown = sysctl -w net.ipv4.ip_forward=0

[Peer]
# 云服务器公钥
PublicKey  = <SERVER_PUBLIC_KEY>
PresharedKey = <PSK>
# 云服务器公网 IP + 端口(用于建立隧道)
Endpoint   = <CLOUD_PUBLIC_IP>:60000

# **只与云服务器通信**(不劫持默认出口)
AllowedIPs = 10.0.5.1/32, <CLOUD_PUBLIC_IP>/32

# 保活
PersistentKeepalive = 25

启动与开机自启

两端分别执行:

# 开机自启
systemctl enable wg-quick@wg0
# 启动
systemctl start wg-quick@wg0
# 查看 'latest handshake' 不为空表示隧道已建立
wg

其他命令

# 重启
systemctl restart wg-quick@wg0
# 关闭
systemctl stop wg-quick@wg0

若你的发行版默认使用 nftables 后端,iptables 命令仍然可用(映射到 nft);无需额外更换。


功能验证与演示

1) 内网机起服务

python3 -m http.server 8080

2) 云侧直连内网机(验证隧道)

curl http://10.0.5.2:8080

3) 外部访问云入口(验证 DNAT/SNAT)

curl -I http://<CLOUD_PUBLIC_IP>:8080

若你的“其他防火墙”默认阻断 wg0 入站,请先按下节加放行规则。


把入口映射给 Docker / 其他内网主机

A. 映射给 Docker

启动容器(示例):

docker run -p 11111:11111 your_image
# 确保容器监听 0.0.0.0:11111(不是仅 127.0.0.1)

统一策略建议用 DOCKER-USER 链,见下节。

B. 再 DNAT 给其他内网主机

例如把 <云公网>:8081 → 192.168.1.100:8080,在内网机添加(可写入 wg0.conf 的 PostUp):

# DNAT:wg0 入站 8081 → 192.168.1.100:8080
iptables -t nat -A PREROUTING -i wg0 -p tcp --dport 8081 \
  -j DNAT --to-destination 192.168.1.100:8080

# 转发放行
iptables -A FORWARD -i wg0 -o eth0 -p tcp -d 192.168.1.100 --dport 8080 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth0 -o wg0 -p tcp -s 192.168.1.100 --sport 8080 -m state --state ESTABLISHED,RELATED -j ACCEPT

由于客户端已做 MASQUERADE-s 10.0.5.0/24 -o eth0),下游主机会把回包发回内网机,再回 wg0 → 云 → 外部。
想保留真实源 IP 需要在下游主机做额外路由返回到内网机,不在本文“最简稳定”方案范围内。


与 UFW/Firewalld/DOCKER-USER 的统一策略

目标:你在“其他防火墙”里怎么允许或禁止端口,通过 WG 访问也保持一致

1) 本机服务(INPUT 链)

UFW 为例(仅允许 wg0 入站访问 8080):

ufw allow in on wg0 to any port 8080 proto tcp
# 若要禁止,就 deny;或保持默认 DROP

Firewalld 示例(接口/区域按你的环境):

firewall-cmd --permanent --zone=public --add-interface=wg0
firewall-cmd --permanent --zone=public --add-port=8080/tcp
firewall-cmd --reload

2) Docker(DOCKER-USER 链)

# 只允许 wg0 访问 11111 端口容器,其他来自 wg0 的容器流量丢弃
iptables -I DOCKER-USER -i wg0 -p tcp --dport 11111 -j ACCEPT
iptables -A DOCKER-USER -i wg0 -j DROP

DOCKER-USER 在 Docker 自身规则之前执行,适合统一“入口策略”。

3) 再 DNAT 到其他内网主机(FORWARD 链)

把你的端口放行/拒绝体现在 FORWARD(或结合 UFW 的 route allow/deny)即可。


排错与自检清单

快速检查

# 两端
wg  # 看 latest handshake 是否正常

# 云:规则命中计数应增长
iptables -t nat -L -v -n
iptables -L -v -n

# 内网机:观察 wg0 是否收到外部 SYN/发出 SYN-ACK
tcpdump -i wg0 tcp port 8080

# Docker 场景:看 docker0 是否收到了包
tcpdump -i docker0 tcp port 11111

常见问题

  • 外部能连云服务器,但到不了内网机
    检查云的 PREROUTING DNAT 是否匹配私网入站 IP<CLOUD_PRIV_IP>)。
  • 偶尔能访问
    确认两端 rp_filter=2all/default/wg0/eth0 都设置了)。
  • Docker 访问不到
    容器是否监听 0.0.0.0DOCKER-USER 是否放行;UFW 是否允许 routed 流量(ufw route allow in on wg0 ...)。
  • 外部 403/超时
    你的“其他防火墙”是否已放行对应端口的 in on wg0;FORWARD/DOCKER-USER 是否放行。

常见问答

Q1:为什么不保留真实客户端 IP?
A:SNAT 把外部源改为 10.0.5.1,换来的是简单 + 稳定 + 无需策略路由。若强需求保留真实 IP,需要在内网机/下游主机加更复杂的策略路由/返回路由,超出本文范围。

Q2:AllowedIPs 用 0.0.0.0/0 可以吗?
A:不建议。“云服务器只做入口”,客户端用最小化 AllowedIPs = 10.0.5.1/32, <CLOUD_PUBLIC_IP>/32,默认出口保持本地。

Q3:iptables 和 nftables 有冲突吗?
A: iptables 通常是 nft 后端,命令可直接用;不需要切 nft 规则。


安全与维护建议

  • 密钥保护:私钥文件权限 600;生产前务必旋转(尤其你曾在公共环境贴出过)。
  • 防火墙优先:不要写“全放行 wg0”的粗暴规则;把端口开在 INPUT/DOCKER-USER/FORWARD 上,保持策略统一。

附:一键生成配置

复制后替换尖括号内容(尤其密钥)再执行。

云(服务端):

cat >/etc/wireguard/wg0.conf <<'EOF'
[Interface]
Address = 10.0.5.1/24
ListenPort = 60000
PrivateKey = <SERVER_PRIVATE_KEY>
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = sysctl -w net.ipv4.conf.all.rp_filter=2
PostUp = sysctl -w net.ipv4.conf.default.rp_filter=2
PostUp = sysctl -w net.ipv4.conf.wg0.rp_filter=2
PostUp = sysctl -w net.ipv4.conf.eth0.rp_filter=2
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
PostUp = iptables -t nat -A PREROUTING -i eth0 -d <CLOUD_PRIV_IP> -j DNAT --to-destination 10.0.5.2
PostUp = iptables -A FORWARD -i eth0 -o wg0 -d 10.0.5.2 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostUp = iptables -A FORWARD -i wg0 -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o wg0 -d 10.0.5.2 -j SNAT --to-source 10.0.5.1
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 -i eth0 -d <CLOUD_PRIV_IP> -j DNAT --to-destination 10.0.5.2
PostDown = iptables -D FORWARD -i eth0 -o wg0 -d 10.0.5.2 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o wg0 -d 10.0.5.2 -j SNAT --to-source 10.0.5.1

[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
PresharedKey = <PSK>
AllowedIPs = 10.0.5.2/32
EOF

内网(客户端):

cat >/etc/wireguard/wg0.conf <<'EOF'
[Interface]
Address = 10.0.5.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
Table = off
PostUp = sysctl -w net.ipv4.ip_forward=1
PostUp = sysctl -w net.ipv4.conf.all.rp_filter=2
PostUp = sysctl -w net.ipv4.conf.default.rp_filter=2
PostUp = sysctl -w net.ipv4.conf.wg0.rp_filter=2
PostUp = sysctl -w net.ipv4.conf.eth0.rp_filter=2
PostUp = iptables -t nat -A POSTROUTING -s 10.0.5.0/24 -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -o eth0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -s 10.0.5.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o eth0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
PostDown = sysctl -w net.ipv4.ip_forward=0

[Peer]
PublicKey  = <SERVER_PUBLIC_KEY>
PresharedKey = <PSK>
Endpoint   = <CLOUD_PUBLIC_IP>:60000
AllowedIPs = 10.0.5.1/32, <CLOUD_PUBLIC_IP>/32
PersistentKeepalive = 25
EOF

到这里,就完成了将公网IP附加给内网主机的内网穿透

消息盒子

# 暂无消息 #

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