Linux 服务器内网服务正常、公网也能访问,但服务器自身访问公网地址超时的解决方法
一、问题背景
服务器上运行了一个 Web 服务,监听一个内网端口。
网络出口设备配置了公网端口映射:
公网地址:PUBLIC_IP:PUBLIC_PORT ↓ 端口映射 / DNAT ↓ 内网地址:PRIVATE_IP:INNER_PORT外部浏览器访问:
http://PUBLIC_IP:PUBLIC_PORT/可以正常打开。
但是,在这台服务器自身访问同一个公网地址时,却无法连接。
二、问题现象
服务器执行:
telnet PUBLIC_IP PUBLIC_PORT一直停留在:
Trying PUBLIC_IP...没有连接成功。
但是执行:
ping PUBLIC_IP却可以正常 ping 通。
也就是说:
ping 公网 IP:正常 TCP 公网端口:服务器自身访问失败需要注意:
ping 通只能说明 ICMP 网络可达,并不能说明对应的 TCP 端口一定能够访问。
三、确认服务本身是否正常
首先测试服务器本机服务:
curl -v --connect-timeout 5 http://127.0.0.1:INNER_PORT/如果返回:
Connected to 127.0.0.1 HTTP/1.1 200说明应用服务本身正常。
继续测试服务器内网地址:
curl -v --connect-timeout 5 http://PRIVATE_IP:INNER_PORT/如果同样返回:
HTTP/1.1 200说明:
127.0.0.1:INNER_PORT 正常 PRIVATE_IP:INNER_PORT 正常如果此时外部浏览器访问:
http://PUBLIC_IP:PUBLIC_PORT/也能正常访问,那么可以确定:
应用服务正常 内网访问正常 公网端口映射正常四、问题原因:NAT 回流
正常外部访问链路是:
外网客户端 ↓ PUBLIC_IP:PUBLIC_PORT ↓ 公网路由器 / 防火墙 DNAT ↓ PRIVATE_IP:INNER_PORT ↓ Web 服务但是服务器自己访问公网地址时,链路变成:
服务器 ↓ PUBLIC_IP:PUBLIC_PORT ↓ 公网出口设备 ↓ 再转回内网 ↓ PRIVATE_IP:INNER_PORT这种场景通常叫:
NAT Loopback Hairpin NAT NAT Reflection NAT 回流简单来说就是:
内网服务器通过公网 IP,再绕回去访问自身所在内网中的服务。
如果出口防火墙或者路由器没有配置 NAT Loopback,就可能出现:
外网访问公网地址:正常 内网访问内网地址:正常 服务器访问公网地址:失败五、不修改程序的解决方法
如果程序中已经固定使用:
http://PUBLIC_IP:PUBLIC_PORT又不希望修改程序配置,可以直接利用 Linuxiptables OUTPUT DNAT。
让服务器自身访问:
PUBLIC_IP:PUBLIC_PORT时,自动转发为:
PRIVATE_IP:INNER_PORT最终效果:
服务器程序 ↓ PUBLIC_IP:PUBLIC_PORT ↓ iptables OUTPUT DNAT ↓ PRIVATE_IP:INNER_PORT ↓ Web 服务程序本身完全不需要修改。
六、添加 iptables DNAT 规则
执行:
iptables -t nat -A OUTPUT \ -p tcp \ -d PUBLIC_IP \ --dport PUBLIC_PORT \ -j DNAT \ --to-destination PRIVATE_IP:INNER_PORT写成一行:
iptables -t nat -A OUTPUT -p tcp -d PUBLIC_IP --dport PUBLIC_PORT -j DNAT --to-destination PRIVATE_IP:INNER_PORT注意:
PUBLIC_IP PUBLIC_PORT PRIVATE_IP INNER_PORT需要替换成自己的实际地址和端口。
七、测试是否生效
执行:
curl -v --connect-timeout 5 http://PUBLIC_IP:PUBLIC_PORT/如果能够看到类似:
Connected to PUBLIC_IP HTTP/1.1 200说明 DNAT 已经生效。
也可以测试:
telnet PUBLIC_IP PUBLIC_PORT如果可以连接,则说明问题已经解决。
八、查看 iptables 规则
执行:
iptables -t nat -L OUTPUT -n -v --line-numbers正常可以看到类似:
DNAT tcp -- 0.0.0.0/0 PUBLIC_IP tcp dpt:PUBLIC_PORT to:PRIVATE_IP:INNER_PORT还可以执行:
iptables -t nat -S OUTPUT查看完整规则。
九、为什么 curl 还是显示公网地址?
即使 DNAT 生效后执行:
curl http://PUBLIC_IP:PUBLIC_PORT/curl 仍然可能显示:
Connected to PUBLIC_IP这是正常的。
因为应用程序请求的目标本身还是:
PUBLIC_IP:PUBLIC_PORT只是数据包进入 Linux 内核网络栈以后,被iptables OUTPUT链修改成了:
PRIVATE_IP:INNER_PORT因此程序本身不需要感知这个转换过程。
这也是这种方案最大的优点:
代码不用改 程序配置不用改 公网 URL 不用改十、iptables 规则默认不会永久保存
直接执行:
iptables -t nat -A OUTPUT ...添加的规则通常只在当前系统运行期间有效。
服务器重启以后,规则可能丢失。
因此还需要设置开机自动添加。
十一、firewalld 和 iptables.service 都没运行的情况
可以先检查:
firewall-cmd --state如果返回:
not running再检查:
systemctl status iptables如果返回:
Unit iptables.service could not be found.说明当前服务器没有使用iptables.service来保存规则。
这种情况下,可以直接使用 systemd 创建一个开机任务。
十二、创建 systemd 服务
创建:
/etc/systemd/system/local-nat.service执行:
cat >/etc/systemd/system/local-nat.service <<'EOF' [Unit] Description=Local NAT Loopback DNAT After=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/sh -c '/usr/sbin/iptables -t nat -C OUTPUT -p tcp -d PUBLIC_IP --dport PUBLIC_PORT -j DNAT --to-destination PRIVATE_IP:INNER_PORT 2>/dev/null || /usr/sbin/iptables -t nat -A OUTPUT -p tcp -d PUBLIC_IP --dport PUBLIC_PORT -j DNAT --to-destination PRIVATE_IP:INNER_PORT' ExecStop=/bin/sh -c '/usr/sbin/iptables -t nat -C OUTPUT -p tcp -d PUBLIC_IP --dport PUBLIC_PORT -j DNAT --to-destination PRIVATE_IP:INNER_PORT 2>/dev/null && /usr/sbin/iptables -t nat -D OUTPUT -p tcp -d PUBLIC_IP --dport PUBLIC_PORT -j DNAT --to-destination PRIVATE_IP:INNER_PORT || true' [Install] WantedBy=multi-user.target EOF注意把:
PUBLIC_IP PUBLIC_PORT PRIVATE_IP INNER_PORT全部替换成自己的实际参数。
十三、为什么使用 iptables -C?
这里没有直接每次都执行:
iptables -A而是先执行:
iptables -C它的作用是检查规则是否已经存在。
逻辑是:
规则存在 ↓ 不重复添加 规则不存在 ↓ 执行 iptables -A这样可以避免每次 systemd 启动时重复添加同一条规则。
十四、启动并设置开机自启
执行:
systemctl daemon-reload启动:
systemctl start local-nat.service设置开机自启:
systemctl enable local-nat.service也可以直接:
systemctl enable --now local-nat.service十五、检查 systemd 状态
执行:
systemctl status local-nat.service正常情况下会显示:
Active: active (exited)这里的:
active (exited)是正常的。
因为该服务使用:
Type=oneshot它只需要执行一次 iptables 命令,并不需要长期驻留后台。
十六、确认开机自启
执行:
systemctl is-enabled local-nat.service如果返回:
enabled就说明服务器重启以后会自动执行这条 NAT 规则。
十七、最终验证
查看 NAT:
iptables -t nat -L OUTPUT -n -v --line-numbers然后执行:
curl -v --connect-timeout 5 http://PUBLIC_IP:PUBLIC_PORT/如果返回:
HTTP/1.1 200则说明配置完成。
十八、最终网络结构
外部用户访问:
外网客户端 ↓ PUBLIC_IP:PUBLIC_PORT ↓ 公网 NAT ↓ PRIVATE_IP:INNER_PORT ↓ Web 服务服务器自身访问:
服务器程序 ↓ PUBLIC_IP:PUBLIC_PORT ↓ iptables OUTPUT DNAT ↓ PRIVATE_IP:INNER_PORT ↓ Web 服务最终实现:
外网仍然使用公网地址 服务器程序仍然使用公网地址 不修改程序 不修改业务配置十九、排查思路总结
遇到类似问题时,可以依次检查:
curl http://127.0.0.1:INNER_PORT/再检查:
curl http://PRIVATE_IP:INNER_PORT/再从真正的外网访问:
http://PUBLIC_IP:PUBLIC_PORT/如果结果是:
localhost 正常 内网 IP 正常 外部访问公网地址正常 服务器自身访问公网地址失败那么就应该优先怀疑:
NAT Loopback Hairpin NAT NAT Reflection NAT 回流而不是一直排查应用、Nginx 或服务端口。
二十、核心命令总结
解决服务器自身访问公网地址的问题:
iptables -t nat -A OUTPUT -p tcp -d PUBLIC_IP --dport PUBLIC_PORT -j DNAT --to-destination PRIVATE_IP:INNER_PORT本质就是:
服务器访问: PUBLIC_IP:PUBLIC_PORT 自动转换为: PRIVATE_IP:INNER_PORT如果服务器重启后也需要生效,再通过 systemd 自动执行即可。
总结
本次问题并不是服务异常,也不是公网端口映射失败。
真正的问题是:
Linux 服务器通过公网 IP 访问自身所在内网的服务时,网络出口设备没有处理 NAT 回流。
最终通过:
iptables OUTPUT DNAT + systemd 开机自动执行解决。
该方案适用于:
程序不能修改访问地址 内外网需要统一使用公网 URL 服务器自身必须访问公网映射地址 出口设备无法方便配置 Hairpin NAT同时不会影响正常的公网访问。