SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

Rocky AlmaLinux firewalld 防火牆設定教學

學會用 firewalld 開放 SSH 與網頁連接埠、關閉連接埠並在重新開機後保留規則,理解 zones 與 --permanent 遺失設定的陷阱。

firewalld 是什麼,以及 Rocky 和 AlmaLinux 為何隨附它

firewalld 是 Rocky Linux、AlmaLinux 及其他 Red Hat Enterprise Linux (RHEL) 重建版本預設安裝的防火牆管理工具。它不會自行檢查封包,而是保存設定,並將設定轉換為 nftables 規則。您可以使用單一指令 firewall-cmd 編輯設定,同時維持伺服器上線。

如果您已了解 Ubuntu VPS 上 ufw 的運作方式,就知道這項工作的目的。firewalld 增加了 ufw 沒有的兩個概念。第一個是 zones:封包會依據名稱分類到不同的政策中。第二個是即時規則與已儲存規則的分離,這由 --permanent 旗標控制,也是此工具最容易造成混淆的部分。

以下所有內容都是在您自己的伺服器上執行的指令。請從第二台機器測試每項變更,因為在伺服器上看似正確的規則,從網際網路連入時仍可能不正確。

先開放 SSH,再進行其他操作

大多數 Rocky 和 AlmaLinux 安裝環境都已存在並執行 firewalld,隨附的設定也允許 SSH。部分精簡版雲端映像檔會移除 firewalld。請先檢查,不要直接假設。

sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
sudo firewall-cmd --state

firewall-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 --reload

target: default 表示不符合任何規則的封包會遭拒絕,並收到 ICMP(Internet Control Message Protocol,網際網路控制訊息協定)的 host-prohibited 回覆。因此,連線到關閉連接埠的用戶端會立即看到 No route to host。將 target 設為 DROP 後,伺服器會改為保持靜默,掃描器也會等待逾時。

sudo firewall-cmd --permanent --zone=public --set-target=DROP
sudo firewall-cmd --reload

執行前請先了解代價:DROP 也會停止伺服器回應 ping,因此您自己的監控也會失去回應。

為什麼規則消失了?--permanent 旗標

firewalld 會同時維護兩份設定。Runtime 設定是 kernel 此刻套用的內容。Permanent 設定儲存在 /etc/firewalld/zones/public.xml,並會在 reload 或重新開機後恢復。

不含 --permanent 的命令只會變更 runtime 設定。變更會立即生效,但下次 reload 或開機時就會消失。含有 --permanent 的命令會寫入檔案,但不會變更目前執行中的設定,因此必須 reload 後連接埠才會開放。這兩種行為都不是錯誤。但兩者都容易造成誤解,因為無論哪一種命令,輸出都會顯示 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 會保留 connection tracking 狀態,因此你的 SSH 工作階段可以持續。--complete-reload 也會重新載入 kernel modules,並清除該狀態,通常會終止所有開啟中的連線,包括你的連線。請使用一般的 reload。

系統內建一項安全機制。Runtime 規則可以自行到期。

sudo firewall-cmd --add-service=http --timeout=5m

這項規則會在五分鐘後自行移除。它不能與 --permanent 合併使用,這正是它的用途:測試你不確定是否正確的變更。較早期的安全做法更可靠。編輯規則時,請保持第二個 SSH 工作階段開啟,直到透過新的登入確認新規則可正常運作後再關閉。

Zone,以及為何 VPS 只需使用 default zone

Zone 是一組具名權限,並附帶信任等級。firewalld 會將每個傳入封包放入且僅放入一個 zone。它會先將封包的來源位址與各 zone 的 sources: 清單比對。如果沒有任何相符項目,則使用傳入介面所綁定的 zone。如果介面未綁定任何 zone,封包就會進入 default zone。

sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones

在只有一個網路介面的 VPS 上,第一個答案幾乎總是 public,而這也是你唯一會使用的 zone。沒有 --zone= 引數的 firewall-cmd 會作用於 default zone,因此本指南中的所有簡短指令都不必指定 zone。

以下是最容易浪費一個下午的問題。如果介面綁定到其他 zone,你的規則會加入 public,但流量卻由另一個 zone 處理,因此新增的規則完全不會生效,也不會收到任何警告。--get-active-zones 會顯示綁定狀態:

public
  interfaces: eth0

如果介面出現在不同的 zone 名稱下,請使用 --zone= 在該 zone 中設定規則,或移動介面。

sudo firewall-cmd --permanent --zone=public --change-interface=eth0
sudo firewall-cmd --reload

Rocky 和 AlmaLinux 上的介面由 NetworkManager 管理,連線啟動時它會重新套用 zone。也要在 NetworkManager 中設定 zone,避免重新開機後設定被還原。請從第一個指令取得連線名稱,因為它通常與裝置名稱不同。

sudo nmcli connection show
sudo nmcli connection modify "System eth0" connection.zone public

來源比對的優先級高於介面比對,因此可以讓單一位址套用不同的政策。內建的 trusted zone 會接受所有流量。

sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.10/32
sudo firewall-cmd --reload

使用這個 zone 時務必小心。它會對該位址開放伺服器上的所有連接埠,包括你原本認為是私有的資料庫。若只想開放單一連接埠,而不是允許整台主機,請使用 rich rule。

什麼是 firewalld service?

service 是以 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

有定義時,請使用 service 名稱。六個月後回頭查看 --list-all 時,這樣更容易理解;此外,Cockpit 等套件也會安裝自己的 service 檔案。沒有定義時,請使用 --add-port

需要注意的限制是:ssh service 只代表 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 會接受所有從 loopback 介面進入的流量,因此無論規則如何設定,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 的 socket 會接受來自任何位址的連線。顯示為 127.0.0.1:443 的 socket 只會在 loopback 上回應,任何防火牆規則都無法讓它從外部連線。Linux 上的連接埠與監聽中的 socket 會更詳細說明這項差異。

如何再次關閉連接埠?

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 主控台,請將其移除。每個開放的連接埠都是你必須持續修補的服務。

將連接埠限制為單一來源位址

Rich rules 是完整格式,適用於單純的服務名稱無法表達需求的情況。若要將 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

Zone 是一組權限,不是遇到第一個相符項目就停止比對的編號清單。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-drop

limit 值可避免大量封包填滿 journal。在將 SSH 限制為單一位址前,請確認該位址穩定不變。家用連線的 IP 位址若會變動,變更當天就可能讓您無法登入,因此請先測試並確認可正常使用服務供應商的主控台存取權。

ufw 指令及其對應的 firewall-cmd 指令

執行相同的工作,但使用不同的工具。每一行 --permanent 後面都需要加入 sudo firewall-cmd --reload,而這正是這類清單無法呈現的部分。

  • sudo ufw enable 會變成 sudo systemctl enable --now firewalld
  • sudo ufw disable 會變成 sudo systemctl disable --now firewalld
  • sudo ufw status verbose 會變成 sudo firewall-cmd --list-all
  • sudo ufw allow OpenSSH 會變成 sudo firewall-cmd --permanent --add-service=ssh
  • sudo ufw allow 443/tcp 會變成 sudo firewall-cmd --permanent --add-port=443/tcp
  • sudo ufw delete allow 443/tcp 會變成 sudo firewall-cmd --permanent --remove-port=443/tcp
  • sudo ufw allow from 203.0.113.10 to any port 22 會變成上方所示的 rich rule
  • sudo ufw reload 會變成 sudo firewall-cmd --reload
  • sudo ufw default deny incoming 已經是 public zone 的行為,而 --set-target=DROP 是它的靜默版本
  • sudo ufw logging on 會變成 sudo firewall-cmd --set-log-denied=all

有一項差異需要明確說明。ufw 會維護編號清單,您可以將規則插入位置 1。firewalld 沒有規則編號,因此「將此規則放在最前面」在這裡沒有意義。當兩個 firewalld 項目看似互相矛盾時,範圍較廣的允許規則會生效,因為規則集中沒有拒絕規則。您必須自行移除範圍較廣的項目。

為什麼防火牆看起來已封閉,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 方式發布連接埠。因此,請從另一台機器進行測試,不要只依賴區域清單。

讓設定在重新開機後持續生效,以及你會看到的錯誤

sudo systemctl is-enabled firewalld
sudo systemctl status firewalld

enabledactive (running) 才是你需要的設定。防火牆正在執行但未啟用時,只能保護系統到第一次重新開機為止。這項檢查應列入你在新 VPS 的前 10 分鐘內逐項確認的清單,並與 SSH 金鑰及更新項目一併處理。

Raw nftables 指令與 firewalld 不可混用。 firewalld 會管理名為 inet firewalld 的 table。sudo nft flush ruleset 會刪除該 table,導致伺服器對所有流量開放;但 firewall-cmd --list-all 仍會顯示你預期的設定,因為 firewalld 回報的是它認為的狀態,而不是 kernel 實際保留的內容。sudo firewall-cmd --reload 會重新安裝規則。請使用 firewall-cmd 寫入規則,這樣在 reload 後規則仍會恢復。

同一台伺服器上執行兩個防火牆管理工具。 在 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。zone 是 public,flag 是 --permanent;唯一可靠的檢查方式,是從另一台機器進行連線測試。

FAQ

為什麼重新開機後,我的 firewalld 規則消失了?

這條規則只加入了 runtime 設定。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 將已儲存的設定載入 kernel 之前,該埠仍會保持關閉。比較 sudo firewall-cmd --list-servicessudo firewall-cmd --permanent --list-services:如果已儲存的清單包含某個項目,而目前生效的清單沒有,表示你缺少重新載入這個步驟。

應該使用 --add-service 還是 --add-port?

如果執行的服務有對應名稱,請使用 --add-service。這能明確表示用途,而 sudo firewall-cmd --info-service=https 會顯示該名稱涵蓋的確切埠號。如果沒有任何名稱可定義你的服務,或服務監聽非標準埠,請使用 --add-portssh 服務僅代表 22/tcp,因此 SSH 改用 2222 時,需要使用 --add-port=2222/tcp,並為該埠設定 SELinux label。

為什麼 firewalld 顯示埠已關閉,但我的 Docker container 仍可連線?

已發布的埠會由 Docker 自己的 NAT 規則改寫,並轉送至 container,因此封包不會交由主機處理;zone 的服務與埠清單只涵蓋交付至主機的封包。即使 --list-all 沒有顯示任何內容,container 仍可從網際網路回應。請改用 docker run -d -p 127.0.0.1:8080:80 nginx 將服務發布至 loopback,並在前方放置 reverse proxy。

可以在 Rocky Linux 上安裝 ufw 取代 firewalld 嗎?

同一台伺服器上的兩個防火牆管理工具會各自寫入規則,彼此不會相互了解;最後由哪個服務啟動,會決定哪一組規則持續生效。firewalld 是 Rocky Linux 與 AlmaLinux 支援的工具,系統已預先安裝,而且使用與 ufw 相同的 nftables backend。只要了解預設 zone 與 --permanent flag,就能掌握整套工具。