Rocky和AlmaLinux VPS firewalld防火墙配置指南
学习在 Rocky 和 AlmaLinux VPS 上使用 firewalld 开放 SSH 和 Web 端口、关闭端口并在重启后保留规则,理解区域与 --permanent 运行时陷阱。
firewalld 是什么,以及 Rocky 和 AlmaLinux 为什么随附它
firewalld 是 Rocky Linux、AlmaLinux 以及其他 Red Hat Enterprise Linux(RHEL)重构发行版默认安装的防火墙管理器。这两个发行版继承了这一默认设置,并非自行选择;了解Rocky 和 AlmaLinux 如何在 CentOS 改变发展方向后重构 Red Hat 的成果后,这一点就更容易理解。firewalld 本身不检查数据包。它保存配置,并将配置转换为 nftables 规则。使用一条命令 firewall-cmd 即可修改配置,同时服务器无需停止服务。本指南中的操作在两个发行版之间没有区别,因为真正区分 Rocky 和 AlmaLinux 的因素是兼容性承诺和仍受支持的 CPU 范围,而不是防火墙。
如果您已经了解ufw 在 Ubuntu VPS 上的工作方式,就了解 firewalld 要完成的任务。firewalld 增加了 ufw 没有的两个概念。第一个是区域:数据包会被分类到命名策略中。第二个是运行时规则与已保存规则的分离,这由 --permanent 标志控制,也是使用此工具时最容易混淆的地方。
下面的每一项都是在您自己的服务器上运行的命令。请从第二台机器测试每项更改,因为服务器上看起来正确的规则,从互联网访问时仍可能存在错误。
首先开放 SSH
大多数 Rocky 和 AlmaLinux 安装默认已安装并运行 firewalld,随附配置也允许 SSH。部分精简版云镜像会移除 firewalld。请先检查,不要想当然。
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --statefirewall-cmd --state 会输出 running。如果服务已停止,其他所有 firewall-cmd 调用都会返回 FirewallD is not running,并以非零状态退出。命令完全没有任何反应时,首先应检查这一点。
现在查看当前已允许的内容。
sudo firewall-cmd --list-all实际输出还会多出几行。以下几行最重要:
public (active)
target: default
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
rich rules:services: 行中的 ssh 是当前会话仍能工作的原因。如果缺少该项,请先添加,再执行其他操作。启动没有 SSH 规则的防火墙会终止当前会话,并且之后无法重新连接。
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reloadtarget: default 表示:不匹配任何规则的数据包会被拒绝,并返回 ICMP(Internet Control Message Protocol,互联网控制消息协议)host-prohibited 响应,因此客户端访问关闭端口时会立即看到 No route to host。将目标设置为 DROP 后,服务器会保持静默,扫描器随后会等待超时。
sudo firewall-cmd --permanent --zone=public --set-target=DROP
sudo firewall-cmd --reload执行前请了解其代价:DROP 还会阻止服务器响应 ping,因此您自己的监控也会失去响应。
为什么我的规则消失了?--permanent 标志
firewalld 同时维护两套配置。运行时配置是内核当前正在执行的配置。永久配置保存在 /etc/firewalld/zones/public.xml 中,并会在 reload 或重启后恢复。
不带 --permanent 的命令只修改运行时配置。它会立即生效,但在下一次 reload 或启动时消失。带 --permanent 的命令会写入文件,但不会修改当前正在运行的配置,因此在 reload 前端口仍保持关闭。这两种行为都不是 bug。由于无论哪种情况命令都会输出 success,因此很容易让人误判。
每次都同时写入这两个配置。
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload您可以分别读取这两套配置。这是最快判断自己犯了哪种错误的方法。
sudo firewall-cmd --list-services
sudo firewall-cmd --permanent --list-services第一条命令输出当前生效的集合。第二条命令输出已保存的集合。如果当前集合包含某项服务,而已保存的集合不包含,那么该规则会在下一次 reload 时消失。如果已保存的集合包含某项服务,而当前集合不包含,那么您忘记执行 reload。sudo firewall-cmd --runtime-to-permanent 会将当前所有配置复制到已保存的文件中,适合在完成一轮实验后使用。
--reload 会保留连接跟踪状态,因此您的 SSH 会话可以继续保持。--complete-reload 还会重新加载内核模块并丢失该状态,通常会终止所有打开的连接,包括您自己的连接。请使用普通的 reload。
系统内置了一项安全措施。运行时规则可以自动过期。
sudo firewall-cmd --add-service=http --timeout=5m该规则会在五分钟后自动删除。它不能与 --permanent 结合使用,这正是其用途:测试您不确定是否安全的更改。旧式安全措施更可靠。修改规则时保持第二个 SSH 会话处于打开状态,直到通过新的登录确认新规则有效后再关闭它。
区域,以及为什么 VPS 上通常只需关注默认区域
区域是一组带有信任级别的命名权限。firewalld 会将每个传入数据包准确分配到一个区域。它首先将数据包的源地址与各区域的 sources: 列表进行匹配。如果没有匹配项,则使用传入接口所绑定的区域。如果接口未绑定任何区域,数据包将进入默认区域。
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones在只有一个网络接口的 VPS 上,第一个命令的结果几乎总是 public,这也是你唯一需要使用的区域。不带 --zone= 参数的 firewall-cmd 会作用于默认区域,因此本指南中的所有简短命令都无需指定区域即可运行。
以下问题经常会浪费整个下午。如果接口绑定到了其他区域,你添加的规则会进入 public,而流量却由其他区域处理,因此新增规则不会产生任何效果,也不会收到任何提示。--get-active-zones 可显示接口绑定关系:
public
interfaces: eth0如果接口显示在其他区域名称下,可以使用 --zone= 在该区域中编写规则,也可以移动该接口。
sudo firewall-cmd --permanent --zone=public --change-interface=eth0
sudo firewall-cmd --reloadRocky 和 AlmaLinux 使用 NetworkManager 管理接口,连接建立时 NetworkManager 会重新应用区域设置。还应在 NetworkManager 中设置该区域,以免重启后配置失效。连接名称应取自第一条命令的输出,因为它通常与设备名称不同。
sudo nmcli connection show
sudo nmcli connection modify "System eth0" connection.zone public源地址匹配优先于接口匹配,因此可以为某个地址应用不同的策略。内置的 trusted 区域允许所有流量。
sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.10/32
sudo firewall-cmd --reload请谨慎使用该区域。它会向该地址开放服务器上的所有端口,包括你以为仅供内部使用的数据库。如果只想开放一个端口,而不是允许一个主机访问所有端口,请使用富规则。
什么是 firewalld 服务?
服务是以 XML 文件发布的命名端口集合。--add-service=https 会开放 443/tcp,因为 /usr/lib/firewalld/services/https.xml 定义了 https 的含义。
sudo firewall-cmd --get-services
sudo firewall-cmd --info-service=https--info-service 会显示该名称对应的端口:
https
ports: 443/tcp如果已有服务名称,请使用名称。这样,六个月后查看 --list-all 时仍然清晰;Cockpit 等软件包也会安装自己的服务文件。对于没有定义的端口,使用 --add-port。
需要注意的一点是:ssh 服务只表示 22/tcp,不包含其他端口。如果您在 加固服务器上的 SSH 访问 时将 SSH 移到了其他端口,那么 --add-service=ssh 不会开放实际使用的端口。
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload在 RHEL 重装系统中,这扇门还受到第二层限制。SELinux(security-enhanced Linux)会为端口号设置标签,sshd 不能绑定到标签范围之外的端口。服务随后会拒绝启动,日志会显示 error: Bind to port 2222 on 0.0.0.0 failed: Permission denied.。请先为端口设置标签。
sudo dnf install -y policycoreutils-python-utils
sudo semanage port -a -t ssh_port_t -p tcp 2222如何查看当前开放的内容?
sudo firewall-cmd --list-all
sudo firewall-cmd --list-rich-rules
sudo nft list table inet firewalld | head -n 40前两条命令显示 firewalld 认为当前开放的内容。第三条命令读取内核实际保存的规则,读取的是由 firewalld 管理的表。这些结果应当一致。
这些结果都不能作为证明。请从另一台计算机进行测试:
nc -zv 203.0.113.20 443不要在服务器本机运行测试。firewalld 会接受通过回环接口进入的所有流量,因此无论规则如何设置,curl http://localhost:8080 都会成功。该测试只能说明服务正在运行,不能说明防火墙配置是否正确。
允许 Web 端口
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-services现在,上一条命令列出的内容中应包含 http https。如果网站仍然无法访问,问题可能不在防火墙。规则只允许数据包通过。仍然需要有进程监听该端口。
sudo ss -tlnp显示为 0.0.0.0:443 或 *:443 的套接字接受来自任意地址的连接。显示为 127.0.0.1:443 的套接字只在回环接口上响应,任何防火墙规则都无法让外部主机访问它。Linux 中的端口和监听套接字对此区别有更详细的说明。
如何再次关闭端口?
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --reload这里同样适用 --permanent 规则,而且在这个方向上影响更大。只从运行时移除服务时,端口看起来已关闭,但下一次重新加载或重启会根据保存的文件再次开放该端口。这个漏洞不容易被发现,因为你运行的检查已经通过。
移除原本不存在的内容时,会输出 Warning: NOT_ENABLED: http,但退出状态仍为 0。重复添加相同内容时,会输出 Warning: ALREADY_ENABLED: http。这两种情况都是安全的。拼写错误的名称则不同:Error: INVALID_SERVICE 表示 firewalld 没有该名称的定义,任何内容都不会更改。
如果你的 --list-all 显示 cockpit,并且不使用 9090 端口上的 Cockpit Web 控制台,请将其移除。每个开放端口都对应一个需要持续安装补丁的服务。对于决定保留的服务,可以使用 dnf-automatic 可按计划自动安装安全更新,这样就不必依赖你记得手动执行这项工作。不过,安装补丁不等于应用补丁;更新完成后,needs-restarting 可显示哪些服务仍在使用旧库。
将端口限制为一个源地址
Rich rule 是长格式,适用于普通服务名称无法表达需求的情况。将 SSH 限制为一个办公地址需要执行两条命令,第二条命令最容易被忽略。
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" service name="ssh" accept'
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload区域是一组权限,而不是匹配到第一条规则后就停止的编号列表。Rich rule 会为一个地址添加允许规则,但不会拒绝其他地址。当 ssh 仍在 services: 行中时,整个互联网仍可访问端口 22,rich rule 不会产生任何可观察的变化。请删除宽泛的规则,否则更具体的规则只是摆设。
对于没有服务名称的端口,请直接指定端口。
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" port port="5432" protocol="tcp" accept'如需丢弃产生大量噪声的网络流量并保留记录,请将 log 元素放在 action 之前;这是 rich rule 语言要求的顺序。
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" log prefix="fw-drop " level="info" limit value="3/m" drop'
sudo firewall-cmd --reload
sudo journalctl -k -g fw-droplimit 值可防止数据包洪泛填满 journal。在将 SSH 限制为单个地址之前,请确认该地址稳定。家庭网络连接的 IP 地址如果会变化,地址变更当天就可能将您锁在服务器之外。因此,请先测试并确认您的服务商控制台访问可用。
ufw 命令及其 firewall-cmd 等效命令
任务相同,工具不同。每行 --permanent 后都需要添加 sudo firewall-cmd --reload,而这是此类列表无法体现的唯一一点。
sudo ufw enable等效于sudo systemctl enable --now firewalldsudo ufw disable等效于sudo systemctl disable --now firewalldsudo ufw status verbose等效于sudo firewall-cmd --list-allsudo ufw allow OpenSSH等效于sudo firewall-cmd --permanent --add-service=sshsudo ufw allow 443/tcp等效于sudo firewall-cmd --permanent --add-port=443/tcpsudo ufw delete allow 443/tcp等效于sudo firewall-cmd --permanent --remove-port=443/tcpsudo ufw allow from 203.0.113.10 to any port 22等效于上面显示的 rich rulesudo ufw reload等效于sudo firewall-cmd --reloadsudo ufw default deny incoming就是publiczone 的现有行为,--set-target=DROP是它的静默版本sudo ufw logging on等效于sudo firewall-cmd --set-log-denied=all
有一点差异需要明确说明。ufw 会维护带编号的规则列表,您可以将规则插入第 1 位。firewalld 没有规则编号,因此“将此规则置于第一位”在这里没有意义。当两个 firewalld 条目看似相互矛盾时,范围更广的 accept 规则会生效,因为规则集中没有任何 deny 规则。您需要自行删除范围更广的条目。
为什么防火墙看似已关闭,Docker 容器仍然可以访问?
因为已发布的容器端口不会经过您所控制的防火墙区域。docker run -d -p 8080:80 nginx 会让 Docker 写入自己的 NAT(网络地址转换)和转发规则。到达 8080 的数据包会被重写并路由到容器,因此它会被转发,而不是投递给主机。区域中的 services: 和 ports: 规则只管理投递给主机的数据包。Docker 的规则管理转发路径,并且会接受这些数据包。
结果是,服务器上的 sudo firewall-cmd --list-all 看不到 8080 端口,但另一台机器执行 nc -zv 203.0.113.20 8080 仍然可以连接。查看 Docker 安装的规则:
sudo iptables -t nat -L DOCKER -n解决方法是修改发布参数。将端口绑定到 loopback,并在其前面配置反向代理。
docker run -d -p 127.0.0.1:8080:80 nginx现在,容器只会在服务器上响应 curl http://127.0.0.1:8080,外部无法访问。Ubuntu 用户也会遇到同样的问题,详见为什么 Docker 容器会绕过 ufw 直接发布端口。Rocky 和 AlmaLinux 在基础软件仓库中提供 rootful Podman;它使用相同的 NAT 方式发布端口。因此,应从另一台机器进行测试,不要只相信区域列表。正因为存在这种重叠,在这些发行版上安装 Docker Engine需要额外执行几步 Ubuntu 指南不会提到的操作,首先是 Podman 已经占用了 docker 命令。
让防火墙在重启后继续生效,以及您会遇到的错误
sudo systemctl is-enabled firewalld
sudo systemctl status firewalldenabled 和 active (running) 是您需要确认的内容。防火墙虽然正在运行,但如果未启用,只能保护服务器到第一次重启为止。您应将这项检查加入 新 VPS 上线后的前十分钟检查清单,并与 SSH 密钥和更新一起完成。
不要混用原始 nftables 命令和 firewalld。 firewalld 管理一个名为 inet firewalld 的表。sudo nft flush ruleset 会删除该表,此后服务器将对所有流量开放;但 firewall-cmd --list-all 仍会显示您预期的配置,因为 firewalld 报告的是它认为的配置,而不是内核实际保存的规则。sudo firewall-cmd --reload 会重新安装这些规则。请使用 firewall-cmd 编写规则,以便规则在重新加载后恢复。
在同一台服务器上运行两个防火墙管理器。 在 firewalld 旁安装 ufw 或 iptables-services,会让两个程序在互不了解的情况下写入规则。最终生效的规则取决于哪个服务最后启动。请选择其中一个。在 Rocky 和 AlmaLinux 上,firewalld 具有发行版支持。
服务器前端的服务商防火墙。 许多 VPS 控制面板都提供独立的网络防火墙。如果 --list-all 显示端口已开放,但从外部发起的连接仍然失败,请先检查控制面板,再修改服务器上的配置。反过来也一样:即使控制面板中的规则允许连接,firewalld 仍可能拒绝数据包。
不使用 sudo 运行 firewall-cmd。 每项更改都需要 root 权限。缺少权限时,请求会被授权检查拒绝,任何配置都不会修改。乍看之下,这很像命令被忽略。
大多数情况下,以下六条命令就够用:使用 --list-all 查看状态,使用 --permanent --add-service 或 --add-port 开放端口,使用 --permanent --remove-service 关闭端口,使用 --reload 应用已保存的文件,并在完成一轮测试后运行 --runtime-to-permanent。区域是 public,标志是 --permanent;只有从另一台机器发起连接,才能得到可靠的检查结果。
FAQ
我的 firewalld 规则为什么在重启后消失?
该规则只写入了运行时配置。sudo firewall-cmd --add-service=http 会立即生效,但在下次重新加载或系统启动时被丢弃,因为从未修改 /etc/firewalld/zones/public.xml 中的已保存配置。添加 --permanent,然后运行 sudo firewall-cmd --reload。若要保留手动添加的现有规则,请运行 sudo firewall-cmd --runtime-to-permanent,将当前生效的规则集复制到已保存的文件中。
为什么使用 --permanent 添加规则后没有任何变化?
因为 --permanent 只写入文件,不会修改正在运行的防火墙。直到 sudo firewall-cmd --reload 将已保存的配置加载到内核之前,该端口仍处于关闭状态。比较 sudo firewall-cmd --list-services 和 sudo firewall-cmd --permanent --list-services:如果已保存的列表中有一条当前生效列表没有的记录,说明缺少重新加载这一步。
应该使用 --add-service 还是 --add-port?
如果运行的服务有对应名称,请使用 --add-service。这能明确表达配置意图,而 sudo firewall-cmd --info-service=https 会显示该名称涵盖的具体端口。如果没有定义对应服务,或服务监听非标准端口,请使用 --add-port。ssh 服务只表示 22/tcp,因此将 SSH 移到 2222 时,需要使用 --add-port=2222/tcp,并为该端口配置 SELinux 标签。
为什么 firewall-cmd 显示端口已关闭,但我的 Docker 容器仍可访问?
Docker 会通过自身的 NAT 规则改写已发布端口,并将流量转发到容器。因此,数据包不会交付给主机,而区域的服务列表和端口列表只适用于交付给主机的数据包。容器仍可从互联网访问,即使 --list-all 没有显示任何内容。请使用 docker run -d -p 127.0.0.1:8080:80 nginx 将端口仅发布到 loopback,然后在容器前部署反向代理。
可以在 Rocky Linux 上安装 ufw 代替 firewalld 吗?
同一台服务器上运行两个防火墙管理器时,它们会在不了解彼此配置的情况下写入规则,最终保留哪一组规则取决于哪个服务最后启动。firewalld 是 Rocky Linux 和 AlmaLinux 支持的工具,系统已预装,并且使用与 ufw 相同的 nftables 后端。掌握默认区域和 --permanent 标志后,就掌握了该工具的核心用法。