概览

关于 iptables 的资料往往比较分散:有的侧重命令,有的侧重表和链,也有的将 ufw、firewalld、nftables 一并讨论。这样虽然信息很多,但读者在实际场景中仍然容易混淆:报文会经过哪条链,规则为什么没有生效,DNAT 已经配置但流量为什么仍然不通。

要真正理解 iptables,关键并不在于记住更多参数,而在于把下面三件事建立起清晰的对应关系:

  • 报文从哪里来,要到哪里去。
  • 它会在内核里的哪些位置被检查。
  • 这些位置上的规则,最终是按什么顺序决定“放行、拒绝、转换地址”。

这篇文章就按这条主线展开:先说明基本原理,再整理常用操作,最后说明 iptables、nftables、ufw、firewalld 之间的关系。

先做一个基础区分:主机防火墙和网络防火墙关注的并不是同一类流量。

  • 如果这台机器只是普通服务器,重点通常在 INPUTOUTPUT
  • 如果这台机器还是路由器、网关或跳板设备,FORWARDnat 表就会变得很重要。

iptables 的基本原理

先分清两个名字:Netfilteriptables

Netfilter 是 Linux 内核里的报文处理框架。所谓“内核里的报文处理框架”,可以先把它理解成 Linux 网络栈里的一组检查点和处理逻辑:报文进来、转发、发出去的过程中,都可以在这些位置被检查、修改、放行或丢弃。

iptables 则是用户在命令行里操作这些规则的工具。也就是说,真正负责检查和处理报文的是 Netfilter,而 iptables 负责把你写下来的规则交给内核。

所以更准确的理解不是“iptables 本身在拦包”,而是:你通过 iptables 配置规则,由 Netfilter 在内核里执行这些规则。

如果只保留一句概括,可以理解为:

一个报文进入或离开 Linux 主机时,会在几个固定位置被 Netfilter 拦下来检查;iptables 做的事,就是告诉内核“在这些位置上,什么流量该怎么处理”。

为了便于后文理解,先看一张整体示意图。这里不必立即记住“五条链、四张表”这些术语,先建立整体认识即可:报文会沿着固定路径流动,并在路径上的不同位置被不同类型的规则处理。

iptables 报文流向示意

先把三个概念分清:表、链、规则

理解 iptables,先区分三个层次:

  • 表:按功能组织规则,例如过滤、地址转换、打标记。
  • 链:报文流经协议栈时,会在固定位置经过一串规则。
  • 规则:一条规则就是“满足什么条件,就执行什么动作”。

初学 iptables 时,比较容易将“表”和“链”混为一体。更合适的理解方式是:

  • 链表示“检查发生在什么时候”。
  • 表表示“这次检查主要想干什么”。

也就是说,链更接近路径上的检查点,表更接近这些检查点上的处理类别。

这时再回看前面的图会更容易理解:图中的固定位置,对应后面会看到的几条链;而在这些位置上执行过滤、地址转换、打标记等不同处理类别,对应后面会看到的几张表。

例如 INPUT 这条链表示“这个包已经被判定为发给本机”;而 filter 表表示“这里主要做的是放行还是拦截”。

最常见的规则形式如下:

iptables -A INPUT -p tcp --dport 22 -j ACCEPT

这条命令的含义是:向 INPUT 链末尾追加一条规则,匹配 TCP 22 端口,命中后放行。

拆开来看:

  • -A INPUT:把规则追加到 INPUT 链。
  • -p tcp --dport 22:匹配目标端口是 22 的 TCP 流量。
  • -j ACCEPT:命中后执行 ACCEPT 动作。

因此可以将一条规则理解为一句条件判断:

如果一个发给本机的 TCP 报文,目标端口是 22,那就放行。

报文是怎么走的

理解报文路径,是掌握 iptables 的基础。

Netfilter 在内核中提供了几个关键钩子点,iptables 的链就挂在这些位置上。

这里的“钩子点”可以简单理解为:报文走到某个固定阶段时,内核会在该位置检查是否存在需要执行的规则。

  • PREROUTING:报文刚进机器,还没做路由判断。
  • INPUT:报文目标就是本机。
  • FORWARD:报文不交给本机,只是借道转发。
  • OUTPUT:报文由本机自己发出。
  • POSTROUTING:报文即将离开机器。

仅记住这五个名称并不足够,更重要的是先将流量分成三类:

  • 发给本机的流量。
  • 经过本机转发的流量。
  • 由本机主动发出的流量。

这三类流量的路径分别是:

  1. 发给本机:PREROUTING -> INPUT
  2. 转发流量:PREROUTING -> FORWARD -> POSTROUTING
  3. 本机主动发出:OUTPUT -> POSTROUTING

理解这三条主要路径后,很多规则位置都可以顺着路径推导出来。对应到常见场景:

  • 访问本机 SSH,主要会匹配 INPUT
  • 本机主动请求外部服务,主要会匹配 OUTPUT
  • 一台 Linux 机器当网关转发内网流量,重点是 FORWARD

再进一步看两个最容易混淆的位置:

  • PREROUTING 在“路由决定之前”,所以常拿来改目标地址,例如 DNAT。
  • POSTROUTING 在“包离开之前”,所以常拿来改源地址,例如 SNAT 或 MASQUERADE。

这也是为什么很多 NAT 规则会写在这两个位置上:地址转换必须发生在合适的处理阶段。

如果机器承担网关或路由职责,还需要确认内核已经开启 IPv4 转发:

# 查看是否开启
cat /proc/sys/net/ipv4/ip_forward

# 临时开启
sysctl -w net.ipv4.ip_forward=1

# 永久开启,建议写到单独的 sysctl 配置文件
printf 'net.ipv4.ip_forward = 1\n' > /etc/sysctl.d/99-ip-forward.conf
sysctl --system

如果没有开启转发,即使 FORWARD 链规则配置正确,机器也不会实际转发流量。

四张最常见的表分别干什么

前面已经提到,链回答的是“什么时候检查”,表回答的是“按什么类型处理”。在这个基础上,再看最常见的四张表会更自然。

iptables 最常接触的是这四张表:

  • filter:做访问控制,决定放行、拒绝还是丢弃。
  • nat:做地址转换,常见于端口映射和出口共享上网。
  • mangle:修改报文标记,用于策略路由、流量整形等场景。
  • raw:在连接跟踪前做特殊处理,平时较少直接接触。

日常服务器运维中,最常用的是 filternat

这里补充一个很关键但经常被忽略的点:不是每张表都会出现在每条链上。

从理解和日常使用的角度,可以先记住下面这组对应关系:

  • filter 最常看的是 INPUTFORWARDOUTPUT
  • nat 最常看的是 PREROUTINGPOSTROUTING,有时也会用 OUTPUT
  • mangleraw 更偏进阶场景。

因此,当需求是“限制 SSH 访问”时,通常应先想到 filter/INPUT;当需求是“将公网 8080 改写到内网 80”时,通常应先想到 nat/PREROUTING。这样更容易快速定位规则位置。

真正让规则变简单的,是连接跟踪

前面讲的是“报文会经过哪条路径、在哪些位置被检查”,但仅有这些还不够,因为真实通信通常不是单个孤立报文,而是一段完整的会话过程。

iptables 的实用性很大一部分来自 Linux 内核的连接跟踪机制,也就是 conntrack

“连接跟踪”这个概念听起来比较抽象,但它的作用并不复杂:内核会记录一条通信当前处于什么阶段。这样在编写规则时,就不必将请求和响应逐条手工对应。

它会帮内核记住某个连接当前处于什么状态,常见状态有:

  • NEW:新的连接请求。
  • ESTABLISHED:已经建立的连接。
  • RELATED:和已有连接相关的流量。
  • INVALID:无法识别或异常的报文。

因此很多规则集会先放一条:

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

这样做的直接好处是,很多响应流量不需要再单独补写规则。

例如:

  • 本机主动访问外部网站,请求包从 OUTPUT 发出去。
  • 返回包进来后会被识别为 ESTABLISHED
  • 因此即使 INPUT 默认策略是 DROP,这类回包仍然可以被前面的连接状态规则放行。

这也是为什么很多规则集看起来只显式开放了请求方向,但通信仍然能够正常完成。原因并不是返回流量被“额外放行”,而是它已经被连接跟踪识别为已有会话的一部分。

一个常见的安全基线写法是顺手把异常流量丢掉:

iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

这条规则并不是所有场景都必须具备,但在面向公网的服务器上,通常值得作为增强项加入。

为什么规则顺序比你想的更重要

iptables 并不会在看完整条链后再统一裁决,而是按照从上到下的顺序依次匹配,命中后立即执行动作。因此,规则顺序不是细节,而是规则语义的一部分。

例如下面这两条规则,顺序一换,含义就完全变了:

iptables -A INPUT -s 10.0.0.5 -j ACCEPT
iptables -A INPUT -j DROP

这表示先放行 10.0.0.5,其他再丢弃。如果把 DROP 放在第一条,后面的放行规则就没有机会生效。

可以将整条链理解为一组按顺序执行的 if/else if 判断:写在前面的规则优先获得匹配机会。

除了规则顺序,还要同时看默认策略,也就是链的 policy。如果一条报文在整条链中都没有命中规则,最后就会落到默认策略上。

因此很多线上环境会把 INPUT 默认策略设为 DROP,然后只显式开放必要端口和可信来源。

常用基本操作

下面按场景整理常用命令,便于查阅和直接套用。

场景 1:先看清当前规则

建议先查看规则,再做修改。

# 查看 filter 表规则
iptables -L -n -v --line-numbers

# 查看 nat 表规则
iptables -t nat -L -n -v --line-numbers

iptables -S
iptables -t nat -S

推荐带上 -n--line-numbers

  • -n 避免 IP 和端口被反解,输出更直接。
  • --line-numbers 方便后续按序号删除或替换规则。

场景 2:新增、删除、替换规则

最常用的是 -A-I-D-R

# 在链首插入一条规则
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT

# 在链尾追加一条规则
iptables -A INPUT -p tcp --dport 80 -j ACCEPT

# 按编号删除
iptables -D INPUT 3

# 按完整规则删除
iptables -D INPUT -p tcp --dport 80 -j ACCEPT

# 替换第 1 条规则
iptables -R INPUT 1 -p tcp --dport 22 -j ACCEPT
  • -I 是插入,默认更靠前。
  • -A 是追加,放到最后。
  • -D 用于删除规则。
  • -R 用于替换指定编号的规则。

如果无法确认当前链中的规则顺序,优先使用 -I 会更稳妥。按编号删除的效率较高,但前提是规则编号已经确认且没有变化。

场景 3:设置一台普通服务器的基础放行规则

下面这组规则适合作为普通服务器的基础起点:

# 回环接口
iptables -A INPUT -i lo -j ACCEPT

# 丢弃明显异常的报文
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 已建立连接和相关连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 放行 SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 放行 HTTP 和 HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

对于常规 Web 服务器,这几条规则通常已经足以构成基础规则集。

如果机器直接暴露在公网,SSH 更建议进一步限制来源地址,或者至少增加速率限制,而不是长期对全网开放。

场景 4:调整默认策略

默认策略通常在基础放行规则之后再设置:

iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

这是一套常见的服务器基线:进入本机和转发默认拒绝,本机主动对外访问默认允许。

远程管理的机器不建议一开始就直接调整默认策略。更稳妥的顺序是先放行 SSH 和已建立连接,再修改默认策略,否则容易导致远程会话中断。

场景 5:做 NAT

前面提到过,nat 表主要处理地址转换。所谓地址转换,可以先概括理解为:报文在经过某个阶段时,源地址或目标地址会被改写。

NAT 常见分为出口伪装和端口转发。

1. 出口伪装

如果一台 Linux 主机需要为后面的内网机器共享上网,最常见的是 MASQUERADE。它可以先理解为“一种根据当前出口地址自动执行源地址改写的方式”:

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

含义是:凡是从 eth0 发出的包,都将源地址改写为当前出口地址。

2. 端口转发

端口转发本质上是改目标地址,也就是通常说的 DNAT。例如把公网 8080 转发到内网主机 10.0.0.10:80

iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.10:80
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -p tcp -d 10.0.0.10 --dport 80 -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

这三条规则分别对应三个不同环节:

  • 第一条在 PREROUTING 改目标地址,把访问公网 8080 的请求改写到内网主机。
  • 第二条放行已建立连接的回包。
  • 第三条允许新的转发请求进入目标主机。

只写 DNAT 而不写 FORWARD 规则时,流量通常仍然无法通过。

再补充一个实战中常见的判断:如果这台执行 DNAT 的机器并不是后端主机的默认网关,回包可能不会再经过它,此时通常还需要额外做 SNATMASQUERADE,以保证返回路径一致。这里的 SNAT 指的是改写源地址,与前面的 DNAT 相对。

场景 6:保存和恢复规则

iptables 规则默认只存在于内存中,机器重启后会消失,因此最终还需要做持久化。

# 导出当前规则
iptables-save > /etc/iptables/rules.v4

# 从文件恢复规则
iptables-restore < /etc/iptables/rules.v4

上面这个路径更常见于 Debian / Ubuntu 体系。实际生产环境中,“保存成功”并不等于“开机自动生效”,还要看系统最终由谁负责加载规则:可能是 netfilter-persistent,也可能是 ufwfirewalldnftables 自身的服务。

至于系统启动时如何自动加载,和发行版关系很大,下一节单独说。

不同发行版、不同防火墙软件之间的关系

这一部分可以拆成三层:

  • 最底层是 Netfilter,在 Linux 内核里。
  • 中间层是 iptablesnftables,它们负责把规则写给内核。
  • 更上层是 ufwfirewalld 这类管理工具,它们提供更容易维护的管理入口。

先看关系图:

Linux 防火墙软件关系图

iptables 和 nftables 到底是什么关系

在老系统中,最常见的仍然是 iptables 语法。大量历史文章、初始化脚本和线上规则模板至今仍以 iptables 为主。

但在较新的发行版中,底层已经越来越偏向 nftables。命令行里的 iptables,有时并不是传统后端,而是兼容 nftables 的入口,也就是常说的 iptables-nft

可以这样看当前系统走的是哪条路:

iptables --version
update-alternatives --display iptables 2>/dev/null || true

如果版本信息里带有 nf_tables,通常说明这套 iptables 命令最终写到的是 nftables 后端。

Ubuntu / Debian:经常先看到 ufw

在 Ubuntu 上,很多人最先接触到的不是 iptables,而是 ufw。它的定位很明确:将开放端口、限制来源、按服务放行等常见操作收敛为更简洁的命令。

例如:

ufw allow 22/tcp
ufw allow 80,443/tcp
ufw status verbose

但要注意,ufw 不是另一套独立的内核防火墙。它本质上只是更上层的管理入口,底层仍然要落到 iptables 或 nftables。

在 Ubuntu / Debian 中,规则持久化常见的是:

  • ufw 自己管理并随服务启动加载。
  • 原生 iptables 方案配合 iptables-persistentnetfilter-persistent

如果已经决定使用 ufw 管理,就不建议再长期手工维护同一套 iptables 规则,以免排障时难以判断究竟是哪一层配置在生效。

RHEL / CentOS / Fedora:更常见的是 firewalld

RHEL 系更常见的是 firewalld。它相较于直接编写 iptables 规则,更强调“区域”和“服务”这些概念,更适合标准化运维和批量管理。

例如开放 HTTP 服务,常见写法是:

firewall-cmd --permanent --add-service=http
firewall-cmd --reload

和 ufw 一样,firewalld 也不是独立内核防火墙,而是更上层的规则管理器。底层在不同版本里可能接 iptables,也可能接 nftables。

这里最需要注意的是:

如果 firewalld 正在接管规则,而同时又手工修改 iptables,最终效果很可能与预期不一致,甚至会被覆盖。在线上环境中,最好选择一个统一入口管理,避免混用。

其他发行版怎么看

  • openSUSE 常见 firewalld。
  • Arch Linux 更常见直接使用 nftables。
  • 老旧系统、存量业务脚本、很多云上初始化脚本仍然大量使用 iptables。

因此实际结论不是“iptables 旧了就不用学”,而是:新系统可以优先理解 nftables,但只要你还在维护线上 Linux,iptables 基本绕不过去。

一套不容易把自己关在门外的最小规则模板

下面这组规则可以作为普通服务器的起步模板,尤其适合远程 SSH 管理场景:

# 先放行本地回环
iptables -A INPUT -i lo -j ACCEPT

# 丢弃异常状态报文
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

# 再放行已经建立的连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 再放行 SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 如果机器要提供 Web 服务,再开放 80 和 443
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

# 最后再收紧默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

这里的顺序不应颠倒。先放行必要流量,再调整默认策略,是远程运维中一条基础但经常被忽略的原则。

如果希望记住这一段,可以将其概括为下面这个顺序:

  1. 先保留本机自身通信和现有会话:loESTABLISHED,RELATED
  2. 再开放你明确需要的入口:SSH、Web、业务端口
  3. 最后再把默认策略收紧为 DROP

这个顺序本身,比单条命令更重要。

小结

iptables 的难点并不在命令本身,而在于需要将报文流向、链的位置、表的职责、规则顺序、连接状态结合起来理解。

如果将全文压缩为一条最重要的理解路径,就是:

  1. 先判断这个流量是发给本机、经过本机,还是由本机发出。
  2. 再判断它会经过哪几条链。
  3. 再判断应该在对应的表里做过滤,还是做地址转换。
  4. 最后再检查规则顺序和默认策略。

在这套逻辑理顺之后,再去看 ufw、firewalld,甚至迁移到 nftables,本质上仍然是在处理同一类问题:报文从哪里来、要到哪里去、会在哪个钩子点被处理,最终是否放行。