
做运维这些年被问得最多的一个问题不是某个中间件怎么调优而是“防火墙到底能不能关”。尤其是新人遇到服务连不上第一反应就是systemctl stop firewalld甚至iptables -F把规则全冲掉。这种操作我太理解了毕竟“关了就能通”是最大的诱惑但代价是把机器裸奔在公网上。今天这篇就围绕 Linux 防火墙管理这个主题把 netfilter 底层的原理、iptables/firewalld/nftables 的选型思路、常用配置命令、性能优化、故障排查完整串一遍。不管你是刚入行的运维还是被线上事故吊打过的老兵应该都能在这里找到点自己用得上的东西。更重要的是我希望你看完之后能形成一套自己的防火墙管理习惯而不是继续靠“开关服务”来解决问题。1. 防火墙的底牌netfilter、表和链1.1 netfilter 不是软件是内核钩子先纠正一个普遍误区Linux 防火墙从来不是iptables这个命令也不是firewalld这个服务而是内核里的 netfilter 框架。iptables和firewalld只是用户态的管理工具真正干活的是内核态。这就好比门禁系统里刷卡机只是交互界面真正检查你有没有权限开门的是后台服务器。数据包从网卡进来之后会沿着一条固定路径在内核里走一遍。这条路径上有五个关键钩子点分别叫做 PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。每个钩子点内核都会发起一场“检查”看用户态有没有下发规则来匹配这个包。匹配上了就按规则动作执行ACCEPT 放行、DROP 丢包、REJECT 拒绝或者跳转到其他规则继续检查。我见过有人把 netfilter 理解成“一堵墙”其实不太准确。它更像是一条流水线上的多个质检工位每个工位负责不同工序地址转换、流量整形、包过滤、连接跟踪。理解了钩子顺序你才能明白为什么 DNAT 规则要写在 PREROUTING而 SNAT 规则要写在 POSTROUTING顺序错了包就会在你意想不到的地方消失。1.2 四表五链其实只需要关注两个表教材里说的“四表五链”经常把人劝退。我的理解方式是链是路表是事。一个数据包走哪条路路上的每个节点有几件事要处理这就是表和链的关系。四个标准表是 raw、mangle、nat、filter每个表在特定钩子点上有自己的链。比如 nat 表的工作集中在 PREROUTING、INPUT、OUTPUT、POSTROUTING 这四条链上filter 表主要在 INPUT、FORWARD、OUTPUT 三条链上。实际运维中90% 的需求只落在 filter 表和 nat 表上raw 和 mangle 表更多用于特殊场景比如给包打标记、绕过 conntrack平时碰得不多。为什么要强调这一点因为规则放错表是特别经典的错误。举个例子你想把公网 IP 的 8080 端口映射到内网一台机器DNAT 规则应该写进 nat 表的 PREROUTING 链。有朋友图省事直接把这条规则写在 filter 表的 INPUT 链上结果外部访问一直不通他自己还纳闷明明规则存在啊。规则在但放错了流水线工位包根本不会经过那里。1.3 iptables、firewalld、nftables 怎么选这三个名字经常把人绕晕。其实它们的关系不是互相替代而是站在同一个 netfilter 地基上的不同操作界面。iptables是最传统的命令语法直观适合一次性排查问题或写脚本。但它的原生规则不动态需要手动保存和加载。firewalld是 CentOS/RHEL 7 之后的默认防火墙服务引入 zone 概念支持动态加载改完规则不用完全重启防火墙而且可以--reload保留当前会话。nftables是新一代框架语法比 iptables 更紧凑性能也更好目前 Debian 和较新的 RHEL 系列都在往它身上迁移。很多人在 CentOS 7 上输入iptables -L能看到规则就以为系统跑的是 iptables实际上 firewalld 的后端可能是 nftables。这不算冲突但你写规则时要意识到firewalld 会管理自己的规则集你直接用iptables命令临时添加的规则重载 firewalld 后很容易被清掉。后文会专门讲这个坑。选型建议新服务器优先用好 firewalld因为它跟系统机制契合支持 zone、富规则、动态重载老服务器继续用 iptables 也没问题只要确认没有 firewalld 在背后干扰。没必要为了赶时髦把稳定的生产机器强行迁到 nftables除非你有明确的性能或语法诉求。2. 从 firewalld 到 iptables常用配置实操2.1 firewalld 的 zone 是一套规则集合firewalld 最核心的概念是 zone我习惯把它理解成“多个隔间”每个网络接口可以划分到不同隔间不同隔间有一套单独的放行规则。默认 zone 是 public意思是这个网络区域是不信任的只能放行极少量的基本服务。实际部署时我通常会把内网接口加到 trusted 区域公网接口留在 public 区域。这样内网访问完全放开公网访问按白名单走。常用操作命令如下firewall-cmd --get-default-zone firewall-cmd --zonepublic --list-all firewall-cmd --permanent --zonepublic --add-port8080/tcp firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.168.10.0/24 port port3306 protocoltcp accept firewall-cmd --reload注意--permanent和--reload的关系。--permanent只把规则写进配置文件不会立即生效必须执行firewall-cmd --reload才会重新加载配置。我见过有人只加了--permanent忘了 reload测试端口不通就以为规则没生效其实规则已经在文件里等着了。反过来如果你在生产环境用临时规则排查问题不要加--permanent改完直接测试测完--reload就可以还原到之前的永久配置。2.2 iptables 命令写法与规则保存虽然新系统都在用 firewalld但iptables命令依然值得熟记因为很多容器环境、NetworkManager 脚本、SDN 网络方案还会直接操作 iptables而且排查问题时用iptables -L看明细非常快。iptables 命令的套路是指定表、指定链、匹配条件、动作。比如最经典的三连iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT iptables -P INPUT DROP第一行是放行回环接口 lo否则本机内部通信都会乱掉SSH 也会受牵连。第二行是放行已建立和相关的连接这是保证 TCP 连接能正常回包的关键。第三行是允许办公网段访问 SSH。最后一行设置默认策略为 DROP意思是其他所有 INPUT 请求一律丢弃。这里有一个小细节-I是插入到前面-A是追加到后面iptables 按顺序匹配所以常用规则要放在前面匹配到你想要的规则时后面就不会继续执行了。保存规则是最容易忽略的环节。裸敲iptables命令只对当前运行的内核生效重启后全部消失。你可以这样保存iptables-save /etc/iptables/rules.v4 iptables-restore /etc/iptables/rules.v4或者在安装了 iptables-services 的发行版上直接systemctl enable iptables service iptables save第一次用 iptables 的时候我就吃过亏半夜加了一条规则以为万事大吉结果主机一重启服务全暴露了。从那以后我再也不敢只敲命令不保存。2.3 配置一台 Web 服务器的标准流程光讲命令不结合场景容易飘我拿一台典型的 Web 服务器举例开放 80/443 给所有人访问SSH 只允许公司办公网段连接其他所有入站请求拒绝。用 firewalld 一步步做最后的效果就是白名单模式。先把默认 zone 设成 public然后添加服务firewall-cmd --set-default-zonepublic firewall-cmd --permanent --zonepublic --add-servicehttp firewall-cmd --permanent --zonepublic --add-servicehttps firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address192.168.1.0/24 port port22 protocoltcp accept firewall-cmd --reload注意我没有直接--add-servicessh因为 ssh 应该只对公司网段开放用富规则更精细。验证规则时先在本机看firewall-cmd --zonepublic --list-all确认 http、https 和 SSH 富规则都在再从外部用nc -vz 服务器IP 80测端口用nc -vz 服务器IP 22测 SSH。外部访问 22 应该超时或拒绝而访问 80 应该成功。这个流程虽然简单但能避免“规则看起来在实际没生效”的错觉。3. 规则优化、日志与攻防细节3.1 规则顺序和 conntrack 对性能的影响防火墙不是无限性能的规则越多、匹配越多损耗越大。尤其在并发量高的场景下规则顺序能直接影响 CPU 使用率。一个核心原则是匹配频率高的规则放前面。比如“放行已建立的连接”这条规则几乎每个回包都会命中所以一定要放在 INPUT 链的最前面。下面这段就很有代表性iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m conntrack --ctstate INVALID -j DROP iptables -A INPUT -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -j DROP第二行把 INVALID 状态直接丢包能避免一些畸形包骚扰应用。这里有一个容易被忽视的性能隐患conntrack 表。系统默认会跟踪所有经过防火墙的连接如果连接数超过表上限新连接就会被丢弃表现是网络时通时不通。可以用下面命令查看和调整sysctl net.netfilter.nf_conntrack_max sysctl -w net.netfilter.nf_conntrack_max262144调大这个值要注意内存消耗每个 conntrack 条目差不多要占用几百字节机器内存不足时别盲目调高。我遇到过一台 2G 内存的机器默认值太小导致高并发下丢包调成 131072 后问题缓解但还是建议同时优化应用本身的连接复用减少新建连接数。3.2 用防火墙拦截暴力破解与恶意流量公网服务器的 SSH 基本每天都会被人扫。除了常规的改端口、禁止 root 登录、改用密钥认证防火墙还可以做第一层粗过滤。iptables 可以用 recent 模块限制单位时间内的新建连接数。下面这套规则是限制每个源 IP 每分钟最多建立 5 个新连接iptables -I INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set --name ssh --rsource iptables -I INPUT -p tcp --dport 22 -m recent --limit 5/minute --update --name ssh --rttl -j ACCEPT iptables -A INPUT -p tcp --dport 22 -j DROP思路是先把新的 SSH 连接标记到 recent 列表再判断这个源 IP 是否超过限制超过的立即丢包。配合 fail2ban 做动态封禁效果更好防火墙负责粗粒度限速fail2ban 负责在多次密码失败后把 IP 拉进黑名单。如果用 firewalld富规则也能实现类似限流firewall-cmd --permanent --add-rich-rulerule familyipv4 source address0.0.0.0/0 port port22 protocoltcp accept limit value5/m要注意任何限流规则都不建议只限制 IP得配合端口。因为同一 IP 背后可能有多个人用一个出口上网限制太死会把正常用户也误伤。我在办公网经常遇到这种问题后来规则就统一改成限制单 IP 的并发连接数而不是总连接次数。3.3 防火墙日志怎么开怎么看默认情况下防火墙丢弃的包不会记录日志这给问题排查带来麻烦。你需要手动加入 LOG 规则并且放在 DROP 规则之前否则包在匹配到 DROP 时就直接丢了根本不会走到日志规则。iptables -A INPUT -p tcp --dport 3306 -j LOG --log-prefix MYSQL-DROP --log-level 4 iptables -A INPUT -p tcp --dport 3306 -j DROP查看日志可以这样tail -f /var/log/kern.log journalctl -k | grep MYSQL-DROP日志量大的时候一定要加过滤否则/var分区会被刷爆。我的做法是给 LOG 规则限定源 IP 或目的端口只记录可疑来源同时配合 rsyslog 把包含特定前缀的日志单独写到独立文件里这样排查起来清爽得多。有人喜欢在 LOG 规则里记录所有 DROP结果五分钟就把磁盘写满了这个坑希望大家别踩。4. 故障排查规则不生效、端口不通、重启丢失4.1 重启后规则丢失的常见原因规则丢失基本就两个原因一是用了临时规则没保存二是系统里同时存在 firewalld 和 iptables 两套管理方式相互覆盖。先说第一种。iptables -A加规则是针对内存里的规则集重启后内核重新初始化规则自然没了。所以持久化的动作必须做用iptables-save写文件或者service iptables save。firewalld 则要求永久规则带--permanent临时规则不带。如果你只加了临时规则且一直没执行--reload那当前会话有效但重载后临时规则也没了。第二种情况坑更大。有些云镜像默认安装了 firewalld但你操作时可能下意识用了iptables命令两者管理的底层规则其实并不完全互通。你通过 iptables 加的一条规则可能在 firewalld 重载时被清理掉也可能因为 firewalld 的 FORWARD 策略默认 DROP 而直接失效。我在一台双网卡服务器上就遇到过iptables 加了 NAT 转发但 firewalld 一 reload转发规则全乱了。后来我统一了管理方式只用 firewalld 的 rich rule 或 direct rule才彻底解决。4.2 Docker 端口映射与防火墙规则冲突Docker 和防火墙的关系是运维里的经典疑难杂症。Docker 启动时会直接操作 iptables在 nat 表和 filter 表插入自己的规则。如果你再用 firewalld 管理端口很容易出现“容器端口明明映射了但外面就是访问不了”的情况。典型表现是docker ps显示 8080 映射到容器 80外部curl却超时。排查时先执行iptables -L FORWARD -n看 FORWARD 链的默认策略是不是 DROP。如果是容器流量就没有被放行。快速解决方法是把 docker0 接口加入 trusted 区域firewall-cmd --permanent --zonetrusted --add-interfacedocker0 firewall-cmd --reload这样做能解决连接问题但要注意把整个 docker0 网桥都信任了所有容器的入站流量都不会被防火墙过滤容器内部有漏洞时风险不小。更精细的做法是单独放行容器所在的子网或端口。如果实在排查不清也可以临时将 Docker 的 iptables 管理关掉在/etc/docker/daemon.json里配置{ iptables: false }但不建议新手这么做因为 Docker 自身的网络隔离依赖 iptables关掉后端口映射和跨容器通信都可能出问题。我通常只在防火墙规则与 Docker 规则冲突严重且可以接受手动管理网络规则的环境里才用这个方案。4.3 端口不通时按这个顺序查网络不通的排查本质上是个剥洋葱的过程。从外到内一层层排除。我的习惯是先ping目标 IP看网络层通不通。如果 ping 不通先查路由和防火墙注意 ping 包也可能被 ICMP 规则过滤。 ping 通后检查目标端口监听状态ss -lntp看服务有没有起来。接着查防火墙规则重点看目标端口对应链的默认策略和放行规则。千万不要只看 INPUT如果机器是 NAT 网关或者 Docker 主机转发流量走的是 FORWARD 链那里也得看。有一个高频错误案例新装了一台 Linux用nc监听了一个端口本机能访问外网访问不了。最后发现公有云安全组把端口禁了跟操作系统防火墙一点关系没有。所以在排查时虚拟化平台或云厂商的安全组一定要先确认别只盯着服务器内部的 iptables 规则看半天。反过来也有安全组全放行但系统内防火墙没配置导致端口不通这类案例我见过不止一次。5. 建立防火墙规则资产的管理习惯5.1 把防火墙配置纳入版本管理防火墙配置跟代码一样需要版本管理。很多团队没有这个习惯服务器上的规则都是“一次性积累”改一次加几条没人说得清每条规则是谁在什么背景下加的。几年下来规则臃肿到没人敢轻易动。我的做法是把规则备份到一个统一目录例如/etc/firewall-backup/每次变更前先导出快照变更后也导出一次文件名带日期和变更说明。备份命令很简单firewall-cmd --permanent --zonepublic --list-all /etc/firewall-backup/public-$(date %F).txt iptables-save /etc/firewall-backup/iptables-$(date %F).rules用 git 管理这些备份文件配合 cron 定时导出出问题时可以直接回滚到某一天的规则状态。这个习惯能帮你省掉很多“昨晚还好好的今天突然不对了”的定位时间。5.2 规则定期清理和审计防火墙规则不是越多越好。过期的端口放行、临时放行没撤销、某次调试加的白名单忘了删这些都是隐患。我通常每季度做一次规则审计把list-all的结果过一遍同时跟业务侧核对端口是否需要继续开放。清理规则时要小心先在测试环境验证而不是直接在生产机器上删。删除 firewalld 规则用--remove-port或--remove-rich-rule删除 iptables 规则要先iptables -L -n --line-numbers找到规则编号再用iptables -D 链名 编号删除。这个操作比添加更容易出错因为删错一条可能直接把管理通道断掉。所以我在删规则前一定会确保自己有其他管理通道比如远程管理卡或者带外控制台否则一旦把 SSH 放行规则误删就只能眼睁睁看着服务器失联。5.3 变更前的自我检查清单到这里聊点我自己习惯用的检查清单。每次变更防火墙前我会确认三件事第一当前规则是否有备份第二是否有保留的远程访问通道第三变更后怎么验证。验证不能只看 “规则已加载”要真的从外部访问测试端口。多敲一条nc -vz比事后救火强太多。做了多年防火墙管理我最深的体会是防火墙不是一个配置完就静态不变的东西它是业务边界的一部分要持续维护。与其花时间研究“怎么关掉防火墙”不如花时间研究“怎么精确放行”。每次看到有人把防火墙关掉然后说“安全靠应用层”我都觉得这是把侥幸当经验。规则透明、变更可审计、回滚有备份这才是运维该有的状态。