iptables 端口转发实战:SNAT/DNAT 四段配置 + 5 个真实故障排查
写防火墙规则最头疼的不是查语法,是加了一条 DNAT,规则明明生效了但外网还是访问不通。写这篇的时候我复盘了从 Nginx 网关到 NAT 边界盒踩过的 5 个坑,都是生产环境真实翻车的场景。
iptables 是 x86 内核 4.x 前的通用防火墙接口,nftables 是 4.11+ 内核的新方案。CentOS 8 / Ubuntu 22.04 / Debian 12 已经默认切 nftables,但 iptables 命令还在(其实是走 nft 兼容层)。本文以 iptables 命令为主,最后一段给 nftables 迁移路径。
0. 60 秒决策:iptables 还是 nftables?
| 场景 | 选 | 理由 |
|---|---|---|
| CentOS 7 / Ubuntu 18.04 及以下 | iptables-legacy | nft 兼容层可能有 bug |
| CentOS 8 / Ubuntu 20.04+ / Debian 11+ | nftables | 已默认,iptables 是 shim |
| 新项目从零开始 | nftables | 更清晰的表/链/规则层次 |
| 老脚本维护 | 继续用 iptables | 兼容层稳定,不用重写 |
判断本机实际引擎:
1. 四段配置对照:DNAT / SNAT / MASQUERADE / REDIRECT
iptables 的 NAT 表有两条链:PREROUTING 做入站地址改写,POSTROUTING 做出站地址改写。这四段配置必须成套用,缺任何一段都会半通半不通。
1.1 DNAT(目标地址改写)— 入站端口转发
外部请求公网 IP:80 → 转发到内网 192.168.1.10:8080:
# 步骤 1:PREROUTING 做 DNAT
iptables -t nat -A PREROUTING -d 203.0.113.10 -p tcp --dport 80 \
-j DNAT --to-destination 192.168.1.10:8080
# 步骤 2:FORWARD 放行(filter 表默认 DROP)
iptables -A FORWARD -d 192.168.1.10 -p tcp --dport 8080 -j ACCEPT
# 步骤 3:反向过滤(回包走 FORWARD OUTPUT 前的检查)
iptables -A FORWARD -s 192.168.1.10 -p tcp --sport 8080 \
-m state --state RELATED,ESTABLISHED -j ACCEPT
# 步骤 4:SNAT 让回程走网关(可选,内网回程已知的场景不需要)
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j MASQUERADE
为什么要 4 步? 因为数据包从外部进入要经过完整的 conntrack 追踪,缺 FORWARD 或反向 ESTABLISHED 规则,包能进但回不来。
1.2 SNAT(源地址改写)— 内网访问外网
# 内网 IP 段整体 SNAT 为公网出口
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 \
-j SNAT --to-source 203.0.113.10
# 特定进程 SNAT(如只让 Nginx 出公网)
iptables -t nat -A POSTROUTING -m owner --uid-owner 33 \
-j SNAT --to-source 203.0.113.10
1.3 MASQUERADE(动态 SNAT)— 出口 IP 会变时用
# DHCP 出口 / CGNAT / 网卡 IP 经常变
iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 \
-j MASQUERADE
MASQUERADE 和 SNAT 的差别:MASQUERADE 不写死源 IP,每次发包时实时读网卡当前 IP 填入,性能好一些(内核 3.5+ 有 conntrack 优化),但规则多了以后 conntrack 表膨胀更明显。
1.4 REDIRECT(本机重定向)— K8s iptables 模式核心
# 发往本机的 443 请求,重定向到本机 8443
iptables -t nat -A PREROUTING -p tcp --dport 443 \
-j REDIRECT --to-port 8443
REDIRECT 是 DNAT 的特例,只作用于本机的 PREROUTING,target 必须和本机 IP 一致(内核自动填本机地址),常用于 K8s iptables 模式的 Service 实现、Nginx/HAProxy 单机多端口映射。
2. 三张表 + 五条链的匹配顺序
新接手一台机器的防火墙,第一件事是把匹配顺序画出来。iptables 有 3 张表,包从网卡进来按固定顺序过:
三张表分别是: - filter 表(默认):做包过滤,DROP/ACCEPT/REJECT - nat 表:只做 DNAT/SNAT/REDIRECT 地址改写 - mangle 表:改包头的 TTL/TOS 等杂项,用于流量整形
关键坑点:包经过每张表时都要匹配所有链,只要任何一条规则命中就立即结束该表(除非用 -j CONTINUE)。所以顺序很重要:具体规则放前面,兜底规则放最后。
3. 排障决策树:5 个真实生产故障
生产环境 90% 的 iptables 不通问题都在这 5 个坑里。按顺序排查,10 分钟内能定位。
3.1 症状一:加了 DNAT 规则但外网 curl 超时
排查路径:
# 1. 确认 PREROUTING 规则存在
iptables -t nat -L PREROUTING -n -v
# 应该有类似:ACCEPT tcp -- * * 203.0.113.10/32 192.168.1.10/32 tcp dpt:80 → 192.168.1.10:8080
# 2. 确认 FORWARD 放行
iptables -L FORWARD -n -v
# 3. 看 conntrack 有没有建立连接
conntrack -L | grep 192.168.1.10
# 4. 抓包看请求到没到网关
tcpdump -i eth0 -nn host 203.0.113.10 and port 80
90% 的原因是:FORWARD 链默认策略是 DROP,或者 FORWARD 链没有匹配这条 DNAT 之后的包。DNAT 改的是目标 IP 和端口,包从 FORWARD 出去之前还在 filter 表判断,这时候目标已经是内网 IP:8080,你的 DNAT 规则里的 --dport 80 匹配不上了。
修法:
(注意是 8080,不是 80)
3.2 症状二:反向路径校验报错 martian
日志里刷 martian source / martian destination,某些转发完全不通。
原因:rp_filter 默认值 1 会检查包的源 IP 是否从同一网卡进来。多网卡 / 多路由表场景下容易判违规。
排查:
cat /proc/sys/net/ipv4/conf/all/rp_filter
cat /proc/sys/net/ipv4/conf/eth0/rp_filter
# 1 = 严格反向路径校验,多网卡时容易误杀
修法(转发网关机器):
# 全部放宽到 loose
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/eth0/rp_filter
# 或者按网卡精细控制
echo 2 > /proc/sys/net/ipv4/conf/eth0/rp_filter # 入站
echo 0 > /proc/sys/net/ipv4/conf/eth1/rp_filter # 转发
写进 /etc/sysctl.d/99-firewall.conf 持久化。
3.3 症状三:SNAT 规则生效但内网某台机器还是走原 IP 出去
排查路径:
# 1. 抓包看源 IP 是啥
tcpdump -i eth0 -nn host 8.8.8.8 and src 192.168.1.50
# 2. 看 conntrack 记录
conntrack -L | grep 192.168.1.50
# 3. 看 POSTROUTING 规则匹配计数
iptables -t nat -L POSTROUTING -n -v
# pkts 是不是 0
常见原因:
- POSTROUTING 规则里 -s 192.168.1.0/24 但机器实际是 10.0.0.0/24
- -o eth0 网卡写错,包从 eth1 出去不匹配
- 有 iptables 前置规则(mangle 表或更早的 nat 规则)已经命中走了别的分支
修法:加 -v 看规则匹配计数,pkts=0 说明规则没生效;查 ip route get 8.8.8.8 看包实际走哪个网卡出去,改 -o 参数。
3.4 症状四:重启后规则丢了
排查路径:
# 1. 备份当前规则
iptables-save > /etc/sysconfig/iptables # CentOS/RHEL
iptables-save > /etc/iptables/rules.v4 # Debian/Ubuntu
# 2. 检查开机加载
systemctl status iptables.service # CentOS
systemctl status netfilter-persistent # Debian/Ubuntu
# 3. 看 rules 文件有没有生成
ls -la /etc/iptables/ /etc/sysconfig/iptables
修法:CentOS 装 iptables-services,Debian/Ubuntu 装 iptables-persistent。规则变更必须 iptables-save 一次才持久化,不然重启就没了。
nftables 用户:nft list ruleset > /etc/nftables.conf,systemctl enable nftables。
3.5 症状五:规则生效但性能突然变差
排查路径:
# 1. 看 conntrack 表使用率
conntrack -L | wc -l
cat /proc/sys/net/netfilter/nf_conntrack_max
# 2. 看规则数量
iptables -S | wc -l
iptables -t nat -S | wc -l
# 3. 看是否有 hashlimit 类重规则
iptables -S | grep -i hashlimit
常见原因:
- nf_conntrack_max 默认 65536 太小,DDoS 或大量短连接时表满,新连接直接被丢
- 规则里 dnat 用 CIDR 段(如 -d 192.168.0.0/16),规则展开成几万个内部规则
- 大量 -m state --state NEW 或 -m limit --limit 规则,每条包都要算
修法:
# 扩 conntrack
echo 262144 > /proc/sys/net/netfilter/nf_conntrack_max
# 优化规则顺序:常见流量放前面,冷门规则放后面
iptables -S > /tmp/rules.bak
# 手动重排后 iptables-restore < /tmp/rules.new
4. Cheat Sheet:20 条常用命令速查
# === 备份恢复 ===
iptables-save # 保存当前所有表
iptables-restore < /etc/sysconfig/iptables
# === 查看 ===
iptables -L -n -v # filter 表,human-readable
iptables -L -n -v -x # 显示计数精确值
iptables -t nat -L -n -v # nat 表
iptables -t mangle -L -n -v # mangle 表
iptables -S # 规则源代码形式
iptables -w -S # 等锁,避免并发问题
# === 修改 ===
iptables -A FORWARD -d 10.0.0.5 -j ACCEPT # 追加
iptables -I INPUT 1 -s 10.0.0.5 -j ACCEPT # 插入到位置 1(最高优先)
iptables -D FORWARD 3 # 删除第 3 条
iptables -R FORWARD 3 -j DROP # 替换第 3 条
iptables -F # 清空所有规则(慎用)
iptables -t nat -F # 清空 nat 表
# === 默认策略 ===
iptables -P FORWARD DROP # 默认策略
iptables -P INPUT ACCEPT # 允许所有入站(临时排查用)
# === 端口转发(内嵌模板) ===
iptables -t nat -A PREROUTING -d $EXT_IP -p tcp --dport $EXT_PORT \
-j DNAT --to-destination $INT_IP:$INT_PORT
iptables -A FORWARD -d $INT_IP -p tcp --dport $INT_PORT -j ACCEPT
iptables -t nat -A POSTROUTING -s $INT_IP -d ! $EXT_NET -j MASQUERADE
# === conntrack ===
conntrack -L # 列出所有连接
conntrack -D -p tcp --dport 22 # 删除指定端口连接
conntrack -S # 统计信息
5. nftables 迁移路径
iptables 语法 → nftables 语法对照:
iptables -A FORWARD -d 192.168.1.10 -p tcp --dport 80 -j ACCEPT
→
nft add rule ip filter FORWARD daddr 192.168.1.10 tcp dport 80 accept
核心差异:
- nftables 有明确的表 / 链 / 规则三层:ip filter INPUT 里的 ip 是表族
- 一条链可以有多个 hook(ipfilter 可以同时挂 PREROUTING 和 INPUT)
- 规则语法更统一,不再有 iptables -t nat 这种"表 + 链 + 目标"的组合
迁移工具:iptables-nft-restore + iptables-save 可以读 iptables 格式(内核 nft 兼容层),也可以 iptables-translate 转换单条规则:
iptables-translate iptables -A FORWARD -d 192.168.1.10 -p tcp --dport 80 -j ACCEPT
# 输出:add rule ip nat prerouting ... accept
新项目的推荐结构(/etc/nftables.conf):
table ip filter {
chain INPUT {
type filter hook input priority 0; policy drop;
ct state established,related accept
ct state new tcp dport 22 accept
iifname "lo" accept
drop
}
chain FORWARD {
type filter hook forward priority 0; policy drop;
ct state established,related accept
daddr 192.168.1.0/24 tcp dport { 80, 443 } accept
drop
}
}
table ip nat {
chain PREROUTING {
type nat hook prerouting priority dstnat;
daddr 203.0.113.10 tcp dport 80 dnat to 192.168.1.10:8080
}
chain POSTROUTING {
type nat hook postrouting priority srcnat;
saddr 192.168.1.0/24 oifname "eth0" masquerade
}
}
迁移踩坑:
1. 规则顺序:iptables 里 -I 插入的顺序在 nft 里也是插入顺序,但 insert 位置不同——nft 默认 append,insert position 1 才是插入头部
2. conntrack 状态:iptables 的 -m state --state RELATED,ESTABLISHED 在 nft 里是 ct state established,related,语法变了
3. 规则组语义:nft 有 {} 组合语法,一条规则可以展开成多个,iptables 没有
6. FAQ
Q1: iptables 端口转发和内网转发有什么区别?
A: iptables 端口转发是 SNAT+DNAT 组合,把外部访问改写到内网某台机器。内网转发是 FORWARD 链放行 + MASQUERADE,让内网机器通过网关访问外网。前者是"进",后者是"出",但都可能涉及 FORWARD 链和 NAT 表。
Q2: -t nat 和 --to 什么关系?
A: -t nat 选择 nat 表,所有 SNAT/DNAT/MASQUERADE 目标都在这张表里。--to 是 DNAT/SNAT 目标参数的写法:-j DNAT --to-destination <IP:PORT> 指定 DNAT 后的目标地址。POSTROUTING 的 SNAT 也是 --to-source <IP>,语义对称。
Q3: 转发规则为什么不生效?先查哪个?
A: 按 3 步查:① iptables -L FORWARD -n -v 看规则 pkts 计数,为 0 说明规则不匹配 ② tcpdump -i <网卡> -nn 抓包看包走到没 ③ sysctl net.ipv4.ip_forward 确认转发开关是 1。90% 情况是第三步的 ip_forward=0。
Q4: iptables 和防火墙软件(firewalld / ufw / nft)什么关系?
A: 都是 iptables 或 nftables 的上层封装。firewalld 是 systemd 时代的 firewalld 服务,操作 nft 表;ufw 是 Ubuntu 的 iptables 前端;nft 是原生 nftables CLI。底层规则是一样的,只是配置语法不同。混用容易冲突,选一个就好。
7. 常见误区
| 误区 | 真相 |
|---|---|
| "iptables 命令就是防火墙" | iptables 只是 CLI,实际执行是内核 nf_tables 或 netfilter |
| "DNAT 改完源 IP 自动走 MASQUERADE" | 不会,SNAT/MASQUERADE 要单独配 POSTROUTING |
| "重启 iptables 服务规则会自动生效" | 只有 save 到持久化文件才生效,重启内存里的规则会丢 |
| "iptables -F 是清空 filter 表" | -F 默认清当前表,不加 -t 就是 filter 表;清空后 INPUT 默认 ACCEPT 除非重设策略 |
| "conntrack 满了会自动扩展" | nf_conntrack_max 是硬上限,满了直接丢新连接,报 nf_conntrack: table full, dropping packet |
8. 关联阅读
- Nginx 反向代理配置 — Nginx 层的端口映射
- Nginx upstream 配置 — upstream 转发与 iptables DNAT 的边界
- TCP 排障决策树 — tcpdump 抓包与 iptables 规则的联动