Linux IP访问控制实战:iptables与firewalld规则详解
半夜收到监控告警某台公网服务器的SSH端口被一个IP连续爆破几百条失败日志刷下来一看就是扫描器在撞库。这种时候多数人的第一反应是iptables -A INPUT -s IP -j DROP先把来源拉黑再说。做运维这几年类似操作我执行过几十次但真正让我印象深刻的反而是那些“拉黑自己”“重启后规则失效”“规则顺序写错导致白名单也进不来”的翻车瞬间。Linux下禁止或允许指定IP的访问本质上就是回答一个问题谁能碰你的服务器、谁不能碰。工具层面不止iptables一个还有firewalld富规则、hosts.allow/hosts.deny、应用层配置等。这篇文章我从实际运维视角把这几条路线的选型逻辑、核心命令、典型坑位一次讲透适合刚开始接手服务器的新人也适合想系统梳理防火墙用法的老手。1. 先分场景再选工具IP访问控制的第一个决策点很多人一上来就抄命令结果越改越乱。我建议动手前先花两分钟想清楚当前需求属于哪一类1.1 三类最常见需求临时封禁、白名单控制、按服务限源第一种是临时封禁。典型场景是某个IP正在扫描、爆破、刷接口你需要立刻把它挡在外面。这类需求的特点是“快”最好一条规则5秒生效同时规则要容易删除因为攻击结束后这些规则会导致误伤或有安全隐患。第二种是长期白名单。典型场景是SSH只允许公司固定出口IP访问或者管理后台只允许网管段访问。这类需求的特点是“稳”规则必须持久化重启不能丢而且要有清晰的维护入口。如果今天封一个IP、明天放一个IP全靠临时命令堆早晚会出问题。第三种是按服务限源。典型场景是Nginx后台只允许内网IPMySQL只允许应用服务器连接监控系统只能从监控机拉取数据。这类需求的特点是“精细”需要同时匹配来源地址和端口、甚至路径光是主机防火墙不一定够。1.2 四条技术路线怎么选iptables、firewalld、TCP Wrapper、应用层在Linux下做IP访问控制常见的有四条路线我把它们的定位和优缺点整理成了表格方案工作层级适用系统优点主要局限iptables内核netfilter几乎所有Linux通用、稳定、规则直接语法偏底层需手动持久化firewalld用户态管理框架RHEL/CentOS/Rocky/Fedora支持zone和富规则管理友好和手动iptables混用会冲突TCP Wrapper应用层libwrap传统Unix/Linux配置极简单按服务控制现代发行版很多已默认不支持应用层配置服务自身所有系统可结合用户、路径精细控制每服务单独配置无全局视角选型的核心原则是看你的系统默认带什么工具看需求属于哪一类选最顺手的那一个就行不必追求“全能”。我自己的习惯是RHEL系优先用firewalldDebian系优先用iptables特殊服务再叠一层应用层限制而不是盲目在每台机器上都搞一套iptables脚本。2. iptables的规则链与实操命令最通用的封禁方案iptables在Linux世界里就像老黄牛稳定可靠但脾气要摸透。它最大的坑不在于“命令难记”而在于“规则顺序”和“默认策略”这两个底层逻辑。2.1 链和匹配顺序先搞懂不然规则写了也白写iptables中有三条默认链和“访问”直接相关INPUT进入本机的包、OUTPUT本机发出的包、FORWARD被本机转发的包。标题里的“禁止或允许指定IP访问”90%的情况都在INPUT链上做文章但如果你的Linux是路由器或负载均衡器真正起作用的是FORWARD链这点很多新手会搞混。更重要的是匹配顺序iptables从上到下逐条匹配命中一条就停止后面的规则不再执行。举个例子INPUT链现有这样三条规则ACCEPT tcp -- 203.0.113.5 tcp dpt:22DROP all -- 192.0.2.10ACCEPT all -- 203.0.113.5来自203.0.113.5的SSH流量会命中第1条直接放行根本走不到第3条来自192.0.2.10的流量会在第2条被丢弃。如果反过来把第2条DROP插到第1条之前那么203.0.113.5的SSH也会被先挡掉。所以白名单ACCEPT规则必须放在DROP规则之前这就是“顺序决定生死”的含义。如果所有规则都不匹配包才会落到链的默认策略-P。默认策略是ACCEPT就是黑名单模式默认策略是DROP就是白名单模式两种模式的操作逻辑完全相反。2.2 禁止/允许指定IP的核心命令与增删改查以下命令就是日常最常用的一套我整理成了速查表操作命令查看规则iptables -L INPUT -n --line-numbers禁止指定IP全部访问iptables -A INPUT -s 192.0.2.10 -j DROP允许指定IP全部访问iptables -A INPUT -s 192.0.2.10 -j ACCEPT禁止指定IP访问22端口iptables -A INPUT -s 192.0.2.10 -p tcp --dport 22 -j DROP允许指定网段访问iptables -A INPUT -s 192.168.10.0/24 -j ACCEPT按编号删除规则iptables -D INPUT 3按具体命令删除规则iptables -D INPUT -s 192.0.2.10 -j DROP插入规则到最前面iptables -I INPUT 1 -s 192.0.2.10 -j DROP几个细节解释一下-s是源IP也就是“谁发起的请求”-d是目标IP一般本机场景不需要写。-p tcp --dport 22是把协议和端口也限制上。如果不写端口就是对该IP的所有入站流量生效。-A是追加到链尾-I是插入到链首。想在已有DROP规则之前插入一条白名单ACCEPT用-I INPUT 1指定插入到第1条。提示远程操作服务器时优先用-I插入到前面的方式放行自己的IP再追加封禁规则能降低把自己关在门外的概率。2.3 DROP和REJECT怎么选这是iptables里争议最多的问题之一。DROP是直接把包丢掉不做任何回应REJECT是丢弃包的同时返回一个拒绝信号。两者的表现差异如下动作客户端表现排错难度适用场景DROP连接一直卡住直到超时像网络不通较难判断SSH防爆破、隐藏端口、对付扫描器REJECT立刻提示Connection refused能明确知道被拒绝内网访问限制、希望客户端快速失败我个人的建议公网的SSH端口做来源限制用DROP。原因很简单扫描器探测到一个超时无响应的端口大概率认为是不可达端口会降低继续扫描的兴致而且DROP不会产生多余的响应包对服务器本身也清净。内网服务建议REJECT比如公司OA只允许办公网访问当同事在咖啡厅连不上时REJECT能让他立刻意识到“这是网络限制”而不是“服务坏了”或者“网线没插好”。2.4 白名单模式把默认策略改成拒绝的正确顺序白名单模式的意思是默认拒绝所有只放行明确允许的IP和服务。这是安全要求较高的服务器的常见配置但也是最容易当场把自己踢出去的配置。正确的操作顺序很重要先看这段# 1. 先放行回环接口 iptables -A INPUT -i lo -j ACCEPT # 2. 放行已建立的连接和关联连接这个必须放行 iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # 3. 放行白名单IP到SSH iptables -A INPUT -s 203.0.113.5 -p tcp --dport 22 -j ACCEPT # 4. 最后才把默认策略改成DROP iptables -P INPUT DROP很多人直接跳到第4步执行iptables -P INPUT DROP结果SSH立刻断掉。原因是TCP是双向的你发出去的数据包走OUTPUT链没问题但服务器返回给你的数据包要重新走INPUT链没有第2步的ESTABLISHED放行这些回包会被默认策略丢掉连接自然就断了。我当时第一次配白名单模式就遇到过这个情况后来养成了习惯只要涉及默认策略变更先确认当前SSH连接状态已经被放行再动-P。2.5 规则持久化RHEL和Debian两种体系的差异iptables命令改完重启后规则默认会丢。持久化方式因发行版而异。RHEL系CentOS、Rocky、AlmaLinux如果要用纯iptables管理建议先停掉firewalld避免双框架冲突systemctl disable --now firewalld yum install -y iptables-services systemctl enable iptables systemctl start iptables iptables-save /etc/sysconfig/iptablesDebian/Ubuntu比较省心装一个iptables-persistent就行apt install -y iptables-persistent netfilter-persistent save netfilter-persistent reload每次修改规则后记得重新save。多数人遗忘的就是这一步导致小心翼翼的规则配完后一重启全没了。3. firewalld富规则与区域现代发行版的IP控制正解如果是RHEL系的较新系统我建议直接用firewalld不要绕道去手工管理iptables。3.1 firewalld和手动iptables不是一回事firewalld是一个用户态防火墙管理框架它自己的规则最终也是通过内核netfilter生效但它提供了更上层的抽象zone、service、rich rule。它的动态特性体现在修改规则后不需要像iptables那样手动保存firewall-cmd reload时会重新加载自己的规则集并且持久化到/etc/firewalld/zones/下的XML文件里。这里有个运维新手经常踩的坑firewalld在跑你又手动用iptables -A加规则当时是生效的但等到firewalld reload或者服务器重启iptables手动加的规则会被清掉。两套工具并存等于给自己埋雷。3.2 区域Zone是策略分流的骨架firewalld把网络接口划分到不同zone每个zone有各自的放行规则。默认zone通常是public特点是“大门紧闭只放行显式允许的服务”。你可以把内网网卡划到internal外网网卡划到public这样策略自然分流。常用查看命令# 查看默认zone firewall-cmd --get-default-zone # 查看默认zone的完整信息 firewall-cmd --list-all # 查看指定zone firewall-cmd --zonetrusted --list-all在做“禁止或允许指定IP”时我们通常不直接改zone里的服务放行而是用**富规则rich rule**做更精确的表达。3.3 富规则指定IP与端口组合的精确控制富规则是firewalld里最接近iptables表达力的功能而且语法比iptables更可读。几个高频场景我直接给代码# 禁止指定IP访问本机所有端口 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.0.2.10 drop # 允许指定IP访问SSH服务 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address203.0.113.5 service namessh accept # 允许指定IP访问8080端口的TCP流量 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address203.0.113.5 port port8080 protocoltcp accept这里最关键的是--permanent。加了它规则写入持久化配置但不会立刻生效需要firewall-cmd --reload才能加载不加它规则立即生效但重启后丢失。查询和删除也很直观# 查看当前所有富规则 firewall-cmd --list-rich-rules # 删除时把完整规则原样写在--remove-rich-rule里 firewall-cmd --permanent --remove-rich-rulerule familyipv4 source address192.0.2.10 drop firewall-cmd --reload注意删除富规则时规则内容必须和添加时完全一致包括引号、空格、顺序。建议用--list-rich-rules的输出直接复制避免肉眼比对误差。3.4 临时与永久规则的配合技巧运维过程中经常出现一种需求同事出差临时需要从某个IP访问服务器明天就结束。如果图省事直接加永久规则事后很容易忘了删。我的做法是临时放行一律不加--permanent让它立即生效且重启自动消失。真正需要长期存在的规则才加--permanent并reload。这样还有一个额外好处每次firewall-cmd --list-all时临时规则和永久规则一目了然审计时有据可查不会出现“规则为什么在这里”的困惑。4. hosts.allow/hosts.deny与TCP Wrapper老办法还剩多少用这条路现在越来越冷门但老系统上还能见到值得讲清楚边界。4.1 原理与匹配优先级TCP Wrapper通过libwrap库让daemon在收到连接时先查两个文件/etc/hosts.allow和/etc/hosts.deny。匹配逻辑是先查allow命中就放行没命中再查deny命中就拒绝都不命中默认放行。注意这个“默认放行”和iptables默认DROP是相反的。配置格式也简单左边是服务名右边是客户端匹配规则。# /etc/hosts.allow sshd: 203.0.113.5 sshd: 10.10.0.0/16 # /etc/hosts.deny sshd: 192.0.2.10如果要做成白名单常规思路是在allow里写白名单在deny里写sshd: ALL。但我不建议在deny里写过于宽泛的ALL: ALL因为一旦allow里有笔误或漏写所有走libwrap的服务都会拒绝连接远程管理直接瘫痪。4.2 先确认服务支持不支持libwrap这个方法最大的问题是现代很多发行版的服务已经不再链接libwrap库。判断方法很简单ldd /usr/sbin/sshd | grep libwrap如果有输出说明sshd支持hosts.allow/deny如果没有输出说明你配置了也没用。Rocky Linux 9、Debian 12这些新版本里我实测过很多默认sshd已经不带libwrap了。所以我的建议是这套老方案适合老系统、自编译的旧服务新装系统别依赖它直接上firewalld或iptables更实际。4.3 现代应用层替代方案sshd_config按来源地址限制如果你一定要在“应用层”限制SSH来源现代sshd本身提供的能力更强# 在sshd_config里只允许指定来源IP AllowUsers admin203.0.113.5 ops10.0.0.0/8 # 或者用Match块做更精细控制 Match Address 192.0.2.10 DenyUsers all配置完记得systemctl reload sshd。这种方式的好处是可以结合用户名比如“只允许admin这个账号从公司出口IP登录”比单纯按IP控制多了一个维度。它适合作为纵深防御的最后一层而不是唯一的防线。5. 远程操作防火墙的翻车现场与救急流程这一节可能是整个主题里最有价值的部分。远程操作防火墙最怕的不是命令记不住而是把自己关在门外。我把自己踩过和被朋友问过的三次典型翻车连同完整的排查链路都写在这里。5.1 把自己IP封了一次操作失误的完整自救场景很常见公司出口IP是203.0.113.66我从跳板机登录服务器想封一个扫描IP 203.0.113.67结果命令里写错了IP把-s 203.0.113.66执行了下去。下一秒SSH就断掉了。自救路线有三个层次第一带外管理。如果是云服务器用云控制台的VNC登录如果是物理机用iDRAC/IPMI。登录后执行iptables -D INPUT -s 203.0.113.66 -j DROP第二求助他人。让机房同事或团队里其他有权限的人帮忙执行同样的删除命令。第三预防胜于救急。我现在的习惯是远程执行任何“封禁类”命令前先给自己留一条定时恢复的后路# 5分钟后自动删除封禁规则给自己留后悔药 echo iptables -D INPUT -s 203.0.113.66 -j DROP | at now 5 minutes如果操作没问题用atq找到任务编号再用atrm 编号取消。这个习惯救了我好几次强烈推荐。5.2 默认策略改DROP后SSH立刻断ESTABLISHED状态没放行我见过不止一个同事执行完iptables -P INPUT DROP后监控立刻告警“服务器失联”。原因前面说过TCP连接是双向的服务器回包也是通过INPUT链进来的默认DROP把它丢了。正确的顺序在2.4节已经给过。这里补充一个更安全的“带验证的改法”# 先以最快的速度确认自己IP能访问22端口 iptables -I INPUT 1 -s 203.0.113.66 -p tcp --dport 22 -j ACCEPT # 再放行已建立连接 iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # 最后改默认策略 iptables -P INPUT DROP这样即使后续规则有问题第一条自己的IP放行仍然兜底。改完测试确认无误后再决定是否保留这条临时ACCETP。5.3 firewalld和iptables混用导致规则丢失这是RHEL系很隐蔽的一个坑。有台CentOS 7服务器firewalld正常运行SSH已经放行。某天发现有人扫描SSH运维图省事直接iptables -A INPUT -s 192.0.2.10 -j DROP当时确实是生效的。但过了几天firewall-cmd --reload后这条规则就消失了扫描流量重新进来。原因是firewalld在reload时会把自己的规则集整体下发同时清理不属于自己管理的规则。手动iptables加的规则不在它的“记忆”里自然被覆盖。结论很明确firewalld运行期间所有规则变更都通过firewall-cmd完成不要混用iptables命令。如果确实需要iptables那就先把firewalld彻底停掉用前面2.5节的iptables-services方案管理。5.4 “连不上”是不是防火墙拦截标准排查链路当客户端报“连不上服务器”先别急着怀疑防火墙有问题。我通常按下面这套顺序排查第一步在客户端测试端口状态nc -vz 10.0.0.5 22如果一直卡住最终timeout大概率是DROP。如果立即返回Connection refused可能是服务没启动也可能是REJECT策略。第二步在服务器上抓包确认tcpdump -nn -i eth0 host 10.0.0.6 and tcp port 22如果能看到客户端的SYN包不断重传但服务器没有回应基本可以断定被DROP了。第三步看iptables计数iptables -L INPUT -n -v --line-numbers观察目标IP对应的DROP规则计数是否在比赛增长。注意这里要加-v才能看到匹配次数的累计。第四步如果是firewalld环境firewall-cmd --list-rich-rules firewall-cmd --list-all被DROP的流量是静默丢弃系统里通常不会留日志所以靠dmesg或journalctl往往看不到线索别浪费时间。正确的判断路径就是“测试端口 抓包 看规则计数”。6. 生产服务器组合配置案例与运维习惯最后给一个可以“抄作业”的综合案例。假设一台公网Web服务器使用Rocky Linux 8跑Nginx提供80/443服务SSH只允许公司固定出口IP和备用运维网段同时经常有恶意IP扫描需要快速封禁且不影响在线业务。6.1 一个典型公网服务器的访问控制需求需求拆开来看就是三层Web服务面向所有公网用户必须放行80和443。SSH管理只允许固定的两个来源属于白名单。恶意IP扫描要能临时拉黑但封禁规则不能长期堆积更不能误伤正常用户。这个需求用firewalld加ipset最合适纯iptables也能实现但firewalld的富规则可读性更好边界更清晰。6.2 用ipset做动态拉黑封禁大量IP的正确姿势当需要封禁的IP数量超过十几条时iptables逐条加规则不仅乱匹配效率也会下降。ipset是内核提供的高性能集合工具配合firewalld的富规则可以做到“一条丢弃规则管一个IP黑名单”。创建带超时时间的黑名单集合ipset create deny_list hash:ip timeout 86400把ipset应用到firewalld富规则firewall-cmd --permanent --add-rich-rulerule familyipv4 source ipsetdeny_list drop firewall-cmd --reload发现恶意IP后直接加进集合ipset add deny_list 192.0.2.10 # 查看当前拉黑列表 ipset list deny_listtimeout 86400的意思是集合中的条目会在86400秒24小时后自动过期相当于临时封禁一天。这个设计适合应对扫描器、爆破攻击攻击时段快速止血又不会因为忘了清理而永久误伤。如果要永久拉黑创建集合时不加timeout要立刻清除某个IP用ipset del deny_list 192.0.2.10。6.3 变更、验证、回滚与版本管理生产环境动防火墙一定要有变更意识。我的标准动作是这样的变更前先备份cp -r /etc/firewalld /root/backup/firewalld-$(date %F)配置完成后验证。除了看规则列表还要从不同网络位置实测# 查看最终规则 firewall-cmd --list-all firewall-cmd --list-rich-rules # 业务侧验证 curl -I http://服务器IP # SSH来源验证 ssh -v 203.0.113.5服务器IP回滚也很直接如果验证发现问题删除刚才添加的富规则或者把备份的zone文件还原再reload。但手动还原文件不如“一条条删除刚才添加的规则”来得快所以我在变更时会把每一条命令都记录在笔记里方便反悔。经验再多说两句。第一防火墙规则文件建议纳入版本管理团队协作时至少把/etc/firewalld/zones/的变更记录进git做到“什么时候、谁、加了什么规则”都有迹可循。第二定期清理一次性放行的临时规则比如每周跑一遍firewall-cmd --list-rich-rules和ipset list deny_list把已经过期的、不再需要的规则清掉。否则半年过去规则越堆越多最后排查问题时全是在给自己制造噪音。我在实际生产环境里管理IP访问控制从来不是“一条命令搞定”的事。它更像一套流程先判断需求类型再选对工具然后按“先放行自己、再放行业务、最后收紧默认”的顺序操作最后做好持久化和回滚预案。把这套流程走熟了防火墙操作就不再是凌晨三点让人心跳加速的冒险。