背景:团队项目需要连接公网服务器上的数据库,数据库监听的端口是 127.0.0.1:5432,服务器由云厂商提供,申请开放的端口为28890(不能自行开放其他端口)。
1. 看似完美的端口映射
在 Linux 服务器上,我们经常需要将非标准端口(如 28890)映射到内部服务端口(如 PostgreSQL 的 5432)。最底层的做法是使用 iptables 的 NAT 机制:
# 外部流量命中 PREROUTING
iptables -t nat -A PREROUTING -p tcp --dport 28890 -j REDIRECT --to-port 5432
# 本机流量命中 OUTPUT
iptables -t nat -A OUTPUT -p tcp --dport 28890 -j REDIRECT --to-port 5432
配置完成后,在服务器本机执行 curl 或 telnet 测试一切正常,但当外部客户端(如 Windows 笔记本)发起连接时,却迟迟无法连通。
2. 抓包揭秘:为什么外部连接会被 RST?
为了定位问题,我们在服务器上使用 tcpdump 抓取 28890 端口的流量。抓包结果给出了关键线索:
- 外部客户端成功发送了 TCP 握手的第一步
SYN包。 - 服务器秒回了
RST+ACK包,主动拒绝了连接。
这说明网络是通的,但服务器内核或应用层拒绝了请求。为什么本机测试正常,外部测试却会被拒绝?这涉及到了 iptables 中两条完全不同的链路:OUTPUT 和 PREROUTING。


3. 核心原理:OUTPUT vs PREROUTING
本机测试走 OUTPUT 链
当我们在服务器本地执行 telnet 220.x.x.x 28890 时,由于目标 IP 就是本机,数据包根本不会经过物理网卡(eth0),而是直接在系统内部产生。
- 流量命中
OUTPUT链,内核在本地直接将目标端口从 28890 修改为 5432。 - 随后,数据包通过本地回环接口(lo)交给 PostgreSQL。因为 PG 默认监听
127.0.0.1,本地回环通信完全没问题,所以连接成功。
外部测试走 PREROUTING 链
当外部客户端发起连接时,数据包是通过物理网卡(eth0)真实进入服务器的。
- 流量命中
PREROUTING链,REDIRECT动作同样将目标端口改为 5432。 - 致命冲突:外部进来的数据包,其目标 IP 实际上是服务器的物理网卡 IP(如
172.20.1.6)。当端口被改为 5432 后,数据包被交给 PostgreSQL,但 PostgreSQL 发现这个包是从外部网卡进来的,且目标不是127.0.0.1,于是内核直接拒绝了该连接,返回了RST包。
四条链路总结
PREROUTING(入站路由前链路)
- 触发时机:数据包刚刚进入网卡,在路由决策之前触发。
- 设计目的:此时系统还不知道这个包是发给自己的,还是需要转交给别人的。因此,这条链路主要用于目的地址转换(DNAT)和端口重定向(REDIRECT)。
- 典型场景:将外部访问服务器 80 端口的流量,悄悄转发到内部的 8080 端口。
INPUT(入站过滤链路)
- 触发时机:数据包经过路由决策后,确认是发给本机进程的,在交给本地应用程序(如 Nginx、PostgreSQL)之前触发。
- 设计目的:充当本机的“门卫”。主要用于包过滤(Filter),决定哪些外部请求可以进入本机。
- 典型场景:只允许特定 IP 访问 22 端口,拒绝所有其他入站连接。
FORWARD(转发过滤链路)
- 触发时机:数据包经过路由决策后,确认不是发给本机的,而是需要由本机转发给其他机器的,在离开本机之前触发。
- 设计目的:充当“路由器”的门卫。主要用于控制跨网段的数据包转发。
- 典型场景:将 Linux 服务器作为软路由,控制哪些内网机器可以访问外网。
OUTPUT(出站路由前链路)
- 触发时机:本机进程主动发出的数据包,在离开本机之前,在路由决策之前触发。
- 设计目的:控制本机发出的流量,或者对本机发出的流量进行 源地址转换(SNAT) 和端口重定向。
- 典型场景:本机访问外部 80 端口时,强制将其重定向到本机的 8080 端口。
POSTROUTING(出站路由后链路)
- 触发时机:数据包即将离开网卡,在路由决策完成之后触发。
- 设计目的:这是数据包离开本机前的最后一道关卡。主要用于源地址转换(SNAT)和IP 伪装(MASQUERADE)。
- 典型场景:内网机器通过这台 Linux 服务器上网,服务器在数据包离开前,将其源 IP 替换为服务器的公网 IP。