防火墙在 Linux 上不是一个软件,而是内核里的一套框架(netfilter)+ 若干种不同的操作界面。
网络通了以后,必须回答:谁允许连我、我允许连谁、包的地址要不要改写。
这三件事(过滤、NAT、转发)都由内核的 netfilter 框架完成。用户态的 iptables、nftables、firewalld、ufw 只是不同的操作界面。
初学者最大的困惑是"到底该学哪个"。答案:它们不是竞争关系,而是分层。
内核态:netfilter(钩子点 + 规则引擎)
↑
│ 同一条规则链的两套语法
┌─────┴─────┐
iptables nftables ← 底层工具(直接操作规则)
(legacy) (现代)
└─────┬─────┘
│ 上层封装
┌───────┼────────┬──────────┐
firewalld ufw fail2ban 云安全组
(RHEL 系)(Debian 系)(动态封 IP)
实践建议:
| 场景 | 选择 |
|---|---|
| 服务器要精细控制 | nftables(现代)或 iptables(老系统兼容) |
| 桌面/简单服务器 | ufw(Debian 系)或 firewalld(RHEL 系) |
| Docker/K8s 环境 | 不要手动管 iptables,Docker 自己会写规则 |
⚠️ 绝对不要混用多套工具。ufw和iptables同时开,规则互相覆盖,结果无法预测。一台机器只用一套。
数据包在协议栈里流动时,会经过固定的几个检查点:
| 钩子 | 时机 | 典型规则 |
|---|---|---|
PREROUTING |
刚进入协议栈,路由决策前 | DNAT(端口转发) |
INPUT |
目标是本机 | 过滤入站 |
FORWARD |
目标是其他机器(转发) | 过滤转发、容器流量 |
OUTPUT |
本机发出 | 过滤出站 |
POSTROUTING |
即将离开协议栈 | SNAT / MASQUERADE |
数据包流向图(记住这张图,规则就不会写错位置):
flowchart LR
N["网卡收到包"] --> PRE["PREROUTING<br/>改目标地址 DNAT"]
PRE --> RT{"路由决策"}
RT -->|目标是本机| IN["INPUT"]
RT -->|目标是他机| FW["FORWARD"]
IN --> APP["本机进程"]
APP --> OUT["OUTPUT"]
OUT --> POST
FW --> POST["POSTROUTING<br/>改源地址 SNAT"]
POST --> W["网卡发出"]
| 概念 | 含义 |
|---|---|
| 表(table) | 按功能分类:filter(过滤)、nat(地址转换)、mangle(改包)、raw(连接跟踪豁免) |
| 链(chain) | 挂在钩子上的规则列表,也支持自定义链 |
| 规则(rule) | 匹配条件 + 动作(target) |
| target | ACCEPT、DROP、REJECT、LOG、SNAT、DNAT、MASQUERADE、RETURN |
| conntrack | 连接跟踪:记住"这条连接是已建立的",返回包自动放行 |
DROP和REJECT的区别很实用:DROP静默丢弃(对方超时,像遇到黑洞),REJECT回一个拒绝包(对方立刻知道)。想隐藏主机用DROP,想让对方快速失败用REJECT。
iptables [-t 表] -命令 链 [匹配条件] -j 动作
默认 filter
| 操作 | 含义 |
|---|---|
-L -n -v |
列出规则(数字显示、带计数) |
-A |
追加到链尾 |
-I [n] |
插入到链首(默认第 1 条) |
-D |
删除 |
-F / -X |
清空链 / 删自定义链 |
-P |
设置链的默认策略 |
-Z |
清零计数器 |
$ sudo iptables -L -n -v --line-numbers
$ sudo iptables -t nat -L -n -v
$ sudo iptables -S # 以可重放格式输出 ← 备份用这个# ① 默认策略:丢弃所有入站/转发,允许出站
# 注意:必须在放行 SSH 之后再设默认 DROP,否则会把自己锁在外面!
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
# ② 放行本机回环
sudo iptables -A INPUT -i lo -j ACCEPT
# ③ 放行已建立的连接 ← 这条最关键,否则返回包全丢
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# ④ 放行具体服务
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# ⑤ 防暴力破解:限制 SSH 新连接频率
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --set --name SSH
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
# ⑥ 丢弃无效包
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# ⑦ 记录被拒绝的包(排障用,上线后建议关掉避免刷日志)
sudo iptables -A INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4# 开启转发(内核层面)
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
# 持久化写 /etc/sysctl.d/99-net.conf: net.ipv4.ip_forward = 1
# 出口 SNAT / MASQUERADE:内网经本机上网
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# 端口转发:外部 8080 → 内网 192.168.1.10:80
sudo iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT \
--to-destination 192.168.1.10:80
sudo iptables -A FORWARD -p tcp -d 192.168.1.10 --dport 80 -j ACCEPT| 动作 | 说明 |
|---|---|
MASQUERADE |
动态源地址转换(出口 IP 会变时用,如拨号) |
SNAT --to-source IP |
固定源地址转换(性能更好,出口 IP 固定时用) |
DNAT --to-destination IP:PORT |
目标地址转换(端口转发) |
REDIRECT |
重定向到本机其他端口(透明代理) |
iptables 的规则默认不持久,重启就没了。
# Debian/Ubuntu
$ sudo apt install iptables-persistent
$ sudo netfilter-persistent save # 存到 /etc/iptables/rules.v4
$ sudo netfilter-persistent reload
# RHEL/Fedora
$ sudo dnf install iptables-services
$ sudo service iptables save # 存到 /etc/sysconfig/iptables
⚠️ 改防火墙最危险的操作是设默认 DROP。远程操作时请务必:
- 先加放行 SSH 的规则;
- 用
at或定时任务做"10 分钟后自动恢复"的兜底;- 或先在本地/控制台会话测试。
一条命令把自己锁在服务器外面的案例,每个运维都有一两个故事。
nftables 从内核 3.13 起就在,现在几乎所有发行版都默认它。优势:统一了 ip/ip6/arp/ebtables,性能更好,语法更一致,原生支持集合。
| iptables | nftables |
|---|---|
| 表 + 链分离 | 表包含链,链指定 type 和 hook |
| 隐式优先级 | 显式 priority |
-j ACCEPT |
accept |
多条 -p tcp --dport 80 |
集合 { 80, 443 } |
| 无原生集合 | set、map、vmap |
ipset 插件 |
内建 |
$ sudo nft list ruleset # 看全部规则 ← 排障第一命令
$ sudo nft list tables
$ sudo nft add table inet filter
$ sudo nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
$ sudo nft add rule inet filter input tcp dport 22 accept
$ sudo nft delete rule inet filter input handle 5
$ sudo nft flush ruleset # ⚠️ 清空所有规则(等于关防火墙)handle 是删除规则时需要的编号:nft -a list ruleset 能看到。
#!/usr/sbin/nft -f
# /etc/nftables.conf ⚠️ 改前备份,并留好 SSH 退路
flush ruleset
table inet filter {
set ssh_bruteforce {
type ipv4_addr
flags dynamic, timeout
timeout 10m
}
chain input {
type filter hook input priority filter; policy drop;
iif lo accept # 回环
ct state established,related accept # 已建立连接
ct state invalid drop
# 允许 ping(限速)
icmp type echo-request limit rate 5/second accept
# SSH:限速 + 自动拉黑
tcp dport 22 ct state new \
add @ssh_bruteforce { ip saddr limit rate 4/minute } accept
ip saddr @ssh_bruteforce drop
# Web
tcp dport { 80, 443 } accept
# 日志
log prefix "nft-drop: " limit rate 5/minute
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}$ sudo nft -c -f /etc/nftables.conf # 语法检查(-c = check)
$ sudo nft -f /etc/nftables.conf # 应用
$ sudo nft list ruleset > /etc/nftables.rules # 备份
$ sudo systemctl enable --now nftables$ sudo iptables-save > rules.v4
$ sudo iptables-restore-translate -f rules.v4 > rules.nft # 自动翻译
$ sudo nft -f rules.nft
$ sudo iptables-nft -L # iptables-nft 兼容层(大多数发行版默认)注意:现代发行版上的
iptables命令其实就是 nftables 的兼容前端(iptables-nft)。所以iptables -L和nft list ruleset看到的是同一批规则的两种视图。这一点不搞清楚会导致严重误判。
$ sudo ufw status verbose
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow 22/tcp
$ sudo ufw allow from 192.168.1.0/24 to any port 3306
$ sudo ufw allow 80,443/tcp
$ sudo ufw limit 22/tcp # 内置限速(防爆破)
$ sudo ufw delete allow 80/tcp
$ sudo ufw enable # ⚠️ 先确保 SSH 已放行
$ sudo ufw disable
$ sudo ufw reset
$ sudo ufw app list # 预定义应用配置Docker 与 ufw 的著名冲突:Docker 直接写
iptables的DOCKER链,绕过 ufw。结果是你 ufw 里禁了 3306,但发布的容器端口照样从公网可访问。解决方案:在/etc/docker/daemon.json里设"iptables": false(需自己管规则)、或把容器端口绑到127.0.0.1:3306:3306、或改用ufw-docker工具。这是新手最常踩的安全坑之一。
核心概念是 zone(区域):不同区域有不同的默认信任级别,接口/源地址被划入某个区域。
| 区域 | 默认行为 |
|---|---|
drop |
丢弃所有入站 |
block |
拒绝所有入站(有回应) |
public |
默认区域,放行 ssh/dhcpv6-client |
trusted |
全部放行 |
$ sudo firewall-cmd --state
$ sudo firewall-cmd --get-active-zones
$ sudo firewall-cmd --list-all
$ sudo firewall-cmd --zone=public --add-service=http --permanent
$ sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
$ sudo firewall-cmd --reload # 应用 permanent 规则
$ sudo firewall-cmd --runtime-to-permanent # 把临时规则固化
$ sudo firewall-cmd --add-masquerade --permanent
$ sudo firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080 --permanent
--permanent与--reload的关系是最大坑点:加--permanent后必须--reload才生效;不加--permanent的规则立即生效但重启丢失。两个都有才叫"现在生效且永久"。
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = 22$ sudo fail2ban-client status
$ sudo fail2ban-client status sshd # 看被封的 IP
$ sudo fail2ban-client set sshd unbanip 1.2.3.4防火墙之外,内核还有一组参数直接决定"包能不能过"。完整清单在 07 章,这里只列与过滤/转发强相关的:
| 参数 | 作用 | 相关场景 |
|---|---|---|
net.ipv4.ip_forward |
是否转发 | NAT、路由、容器 |
net.ipv4.conf.all.forwarding |
所有接口转发 | 同上 |
net.bridge.bridge-nf-call-iptables |
桥接流量过不过 iptables | Docker/K8s 必需 1 |
net.ipv4.conf.all.rp_filter |
反向路径校验 | 多网卡/非对称路由会误杀 |
net.ipv4.tcp_syncookies |
SYN Flood 防护 | 保持 1 |
net.netfilter.nf_conntrack_max |
连接跟踪表上限 | 高并发时"表满丢包" |
net.ipv4.ip_local_port_range |
本地端口范围 | 出站连接数受限 |
$ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
$ dmesg | grep -i "conntrack: table full" # ← 高并发丢包的隐蔽元凶
$ sudo conntrack -L | wc -l # 当前连接跟踪条目数关键认知:容器网络依赖 iptables/nftables 的 NAT 与 FORWARD 链。
$ sudo iptables -t nat -L DOCKER -n --line-numbers # Docker 写的规则
$ sudo iptables -L DOCKER-USER -n -v # ← 你的自定义规则放这里| 要点 | 说明 |
|---|---|
| Docker 会改 FORWARD 链策略 | 可能覆盖你的默认策略 |
DOCKER-USER 链是留给你的入口 |
自定义规则应插在这里,且 Docker 不会覆盖它 |
| 端口发布绕过 ufw | 见 5.1 的说明 |
| 重启防火墙服务可能清掉 Docker 规则 | 需重启 docker 服务恢复 |
| K8s 用自己的 kube-proxy / CNI | 不要手改,改 Calico/Cilium 的配置 |
# 只允许特定网段访问容器的 3306
$ sudo iptables -I DOCKER-USER -p tcp --dport 3306 ! -s 192.168.1.0/24 -j DROP| 症状 | 排查 |
|---|---|
| 规则不生效 | 是否 reload?iptables -L -n -v 看计数器有没有涨 |
| 规则写对了但包不匹配 | 位置错了(-A 在链尾,前面有 DROP 就轮不到);用 -I 插到前面 |
| 改了规则把自己锁外面 | 用带外/控制台,或 nft flush ruleset / 临时 -P ACCEPT |
| NAT 不生效 | ip_forward 没开;FORWARD 链被 DROP;rp_filter 太严 |
| 返回包收不到 | 少了 conntrack ESTABLISHED,RELATED 放行 |
| Docker 端口意外公网可访问 | ufw 被绕过(见 5.1) |
| 高并发下偶发丢包 | conntrack 表满(dmesg 有提示) |
| 规则重启就没了 | 没做持久化 |
# 带计数器的实时观察 ← 最有效的验证手段
$ sudo iptables -L -n -v --line-numbers
$ sudo watch -n1 'iptables -L INPUT -n -v | head -20'
$ sudo nft list ruleset -a
$ sudo conntrack -L | head调试技巧:临时全放行 + 日志。先用
LOGtarget(不是 DROP)记录"本该被拦的包",观察一段时间确认规则正确,再改成 DROP。这样不会误伤。
iptables和nftables在多数系统上是同一套规则的不同视图(iptables-nft 兼容层)。- 默认策略
DROP忘放行 SSH = 自锁。 - ufw/firewalld 与 iptables 不要混用。
- Docker 绕过 ufw,这是真实的安全漏洞来源。
- 防火墙不是万能安全措施,它只管端口层,应用层漏洞照样被打。
- 规则顺序决定一切,匹配到第一条就返回。
- nftables 高级特性:map / vmap、动态集合、
ct对象完整用法 -
conntrack调优与监控(高并发场景) - 透明代理(TPROXY)与
REDIRECT - 云环境安全组与主机防火墙的协作
- nftables 与 Docker/K8s 的冲突处理
-
firewalld高级:rich rules、direct interface - 网络策略(NetworkPolicy)与 CNI 层面的过滤
- 用
bpfilter/ XDP 做高性能过滤 - DDoS 防护分层方案