半夜收到监控告警,某台公网服务器的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后台只允许内网IP,MySQL只允许应用服务器连接,监控系统只能从监控机拉取数据。这类需求的特点是“精细”,需要同时匹配来源地址和端口、甚至路径,光是主机防火墙不一定够。
1.2 四条技术路线怎么选:iptables、firewalld、TCP Wrapper、应用层
在Linux下做IP访问控制,常见的有四条路线,我把它们的定位和优缺点整理成了表格:
| 方案 | 工作层级 | 适用系统 | 优点 | 主要局限 |
|---|---|---|---|---|
| iptables | 内核netfilter | 几乎所有Linux | 通用、稳定、规则直接 | 语法偏底层,需手动持久化 |
| firewalld | 用户态管理框架 | RHEL/CentOS/Rocky/Fedora | 支持zone和富规则,管理友好 | 和手动iptables混用会冲突 |
| TCP Wrapper | 应用层libwrap | 传统Unix/Linux | 配置极简单,按服务控制 | 现代发行版很多已默认不支持 |
| 应用层配置 | 服务自身 | 所有系统 | 可结合用户、路径精细控制 | 每服务单独配置,无全局视角 |
选型的核心原则是:看你的系统默认带什么工具,看需求属于哪一类,选最顺手的那一个就行,不必追求“全能”。我自己的习惯是:RHEL系优先用firewalld,Debian系优先用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 --zone=trusted --list-all在做“禁止或允许指定IP”时,我们通常不直接改zone里的服务放行,而是用**富规则(rich rule)**做更精确的表达。
3.3 富规则:指定IP与端口组合的精确控制
富规则是firewalld里最接近iptables表达力的功能,而且语法比iptables更可读。几个高频场景我直接给代码:
# 禁止指定IP访问本机所有端口 firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.0.2.10" drop' # 允许指定IP访问SSH服务 firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.5" service name="ssh" accept' # 允许指定IP访问8080端口的TCP流量 firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.5" port port="8080" protocol="tcp" accept'这里最关键的是--permanent。加了它,规则写入持久化配置,但不会立刻生效,需要firewall-cmd --reload才能加载;不加它,规则立即生效,但重启后丢失。
查询和删除也很直观:
# 查看当前所有富规则 firewall-cmd --list-rich-rules # 删除时把完整规则原样写在--remove-rich-rule里 firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="192.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 admin@203.0.113.5 ops@10.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-rule='rule family="ipv4" source ipset="deny_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访问控制,从来不是“一条命令搞定”的事。它更像一套流程:先判断需求类型,再选对工具,然后按“先放行自己、再放行业务、最后收紧默认”的顺序操作,最后做好持久化和回滚预案。把这套流程走熟了,防火墙操作就不再是凌晨三点让人心跳加速的冒险。