通过 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
- wireguard:
wg
/wg-quick
工具与内核模块 - iproute2:
ip
/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=2
(all/default/wg0/eth0
都设置了)。 - Docker 访问不到
容器是否监听0.0.0.0
;DOCKER-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附加给内网主机的内网穿透