SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-09-04

Rocky AlmaLinux VPS firewalld 防火牆設定教學

學會在 Rocky 或 AlmaLinux VPS 使用 firewalld 開放 SSH 與網頁埠、關閉埠口並在重開機後保留設定,理解 zones 與 --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 比 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:

ssh 出現在 services: 行,這就是目前工作階段仍能運作的原因。如果缺少該項目,請先新增,再進行其他操作,因為在沒有 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。firewall-cmd 未指定 --zone= 引數時,會作用於 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

有對應名稱時,請使用該名稱。六個月後回頭查看 --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 管理的 table。兩者應該一致。

這些結果都不能作為證明。請從另一台機器進行測試:

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 主控台,請將它移除。每個開放的連接埠都代表你必須持續修補的服務;對於決定保留的服務,dnf-automatic 可依排程安裝安全性更新,讓這項工作不必依賴你記得執行。不過,安裝修補程式不等於套用修補程式;更新完成後,needs-restarting 會顯示哪些服務仍在使用舊版程式庫

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

Rich rules 是完整形式,適用於單純的服務名稱無法表達需求的情況。若要將 SSH 限制為單一辦公室位址,需要執行 2 個命令,而第 2 個命令常被忽略。

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 是一組權限,不是從第 1 個比對項目開始、比對到即停止的編號清單。Rich rule 會針對單一位址新增允許規則,但不會拒絕其他來源。當 ssh 仍位於 services: 行中時,整個網際網路仍可連線至 port 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 container 卻仍可連線?

因為 published container port 不會經過 zone 所控制的防火牆部分。docker run -d -p 8080:80 nginx 會指示 Docker 寫入自己的 NAT(network address translation)與轉送規則。封包抵達 8080 後,會被改寫並轉送至 container,因此屬於轉送流量,不是傳送至主機本身。zone 中的 services:ports: 行只會管控傳送至主機的封包。Docker 的規則則管控轉送路徑,而且會接受這些封包。

結果是,伺服器上的 sudo firewall-cmd --list-all 看不到 8080 埠,但另一台機器執行 nc -zv 203.0.113.20 8080 仍可連線。請查看 Docker 安裝的規則:

sudo iptables -t nat -L DOCKER -n

修正方式是調整 publish flag。將埠繫結至 loopback,並在前方放置 reverse proxy。

docker run -d -p 127.0.0.1:8080:80 nginx

現在 container 只會在伺服器上的 curl http://127.0.0.1:8080 回應,外部無法連線。Ubuntu 使用者也會遇到相同問題,詳見 為什麼 Docker container 會直接繞過 ufw 發布埠。Rocky 和 AlmaLinux 在 base repositories 中提供 rootful Podman;它同樣使用 NAT 方式發布埠,因此請從另一台機器進行測試,不要只相信 zone 清單。這項重疊也是 在這些發行版上安裝 Docker Engine 需要額外步驟的原因之一,第一步就是處理 Podman 已經擁有 docker command 的情況。

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

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 寫入規則,這樣重新載入後規則仍會恢復。

同一台伺服器上使用兩個防火牆管理工具。 在 firewalld 旁安裝 ufw 或 iptables-services,會讓兩個程式彼此不知情地寫入規則,最後採用哪一組規則則取決於哪個服務最後啟動。請選擇其中一個。在 Rocky 和 AlmaLinux 上,firewalld 具備發行版支援。

伺服器前方的供應商防火牆。 許多 VPS 控制面板另有獨立的網路防火牆。如果 --list-all 顯示某個連接埠已開放,但外部連線仍失敗,請先檢查控制面板,再修改伺服器上的設定。反過來也一樣:即使控制面板中的規則已開放,firewalld 拒絕封包時仍然無法連線。

未使用 sudo 執行 firewall-cmd。 每項變更都需要 root 權限。沒有 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,因此封包不會交付給 host;zone 的 service 與 port 清單只涵蓋交付給 host 的封包。即使 --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,就能掌握整套工具。