RockyやAlmaLinuxのVPSでfirewalldを設定する方法
RockyやAlmaLinuxのVPSでfirewalldを使い、SSHやWebポートを安全に開閉する方法を解説します。ゾーン、再起動後も維持する--permanentの注意点も確認できます。
firewalld とは何か、Rocky と AlmaLinux で採用されている理由
firewalld は、Rocky Linux、AlmaLinux、および Red Hat Enterprise Linux (RHEL) のその他のリビルドでデフォルトでインストールされるファイアウォール管理ツールです。firewalld 自体がパケットを検査するわけではありません。保存済みの設定を管理し、その設定を nftables のルールに変換します。firewall-cmd という 1 つのコマンドで、サーバーをオンラインのまま設定を変更できます。
Ubuntu VPS で ufw がどのように動作するかをすでに理解していれば、役割は同じだと分かります。firewalld には、ufw にはない 2 つの考え方があります。1 つ目はゾーンです。これは、パケットを振り分ける名前付きのポリシーです。2 つ目は、現在有効なルールと保存済みのルールを分けて管理する仕組みです。これは --permanent フラグで指定し、このツールで最も混乱しやすい点です。
以下はすべて、自分のサーバーで実行するコマンドです。変更は必ず 2 台目のマシンからテストしてください。サーバー上では正しく見えるルールでも、インターネットから見ると誤っている場合があります。
他の作業を行う前に 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 と応答し、0 以外の終了ステータスで終了します。コマンドが何も実行していないように見える場合、最初に確認すべき点です。
現在許可されている内容を確認します。
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 が返ります。target を DROP にすると、サーバーは応答せず、スキャナーはタイムアウトまで待機します。
sudo firewall-cmd --permanent --zone=public --set-target=DROP
sudo firewall-cmd --reload実行する前に影響を把握してください。DROP はサーバーが ping に応答することも停止するため、監視も停止します。
ルールが消えたのはなぜですか?--permanent フラグ
firewalld は、2 つの設定を同時に保持します。runtime 設定は、現在カーネルが適用している設定です。permanent 設定は /etc/firewalld/zones/public.xml に保存され、reload または再起動後に復元される設定です。
--permanent を付けないコマンドは、runtime 設定だけを変更します。変更はすぐに反映されますが、次回の reload または起動時に消えます。--permanent を付けたコマンドはファイルに書き込みますが、実行中の設定は変更しません。そのため、reload するまでポートは閉じたままです。どちらも不具合ではありません。どちらの場合もコマンドは success と表示されるため、混乱しやすいだけです。
毎回、この 2 つを組み合わせて記述してください。
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload2 つの設定を確認できます。どちらの間違いをしたのかを最も早く確認する方法です。
sudo firewall-cmd --list-services
sudo firewall-cmd --permanent --list-services1 つ目は現在有効な設定を表示します。2 つ目は保存済みの設定を表示します。現在有効な設定にサービスがあり、保存済みの設定にない場合、そのルールは次回の reload で消えます。保存済みの設定にルールがあり、現在有効な設定にない場合は、reload を忘れています。sudo firewall-cmd --runtime-to-permanent は現在有効な設定をすべて保存ファイルにコピーします。設定を試した後に使用すると便利です。
--reload はコネクション追跡の状態を保持するため、SSH セッションは維持されます。--complete-reload はカーネルモジュールも再読み込みし、その状態を失わせます。通常は、自分の接続を含むすべてのオープンな接続が切断されます。通常の reload を使用してください。
安全策も用意されています。runtime ルールは自動的に期限切れにできます。
sudo firewall-cmd --add-service=http --timeout=5mこのルールは 5 分後に自動的に削除されます。--permanent と併用できません。これは、確信のない変更をテストするための機能だからです。従来の安全策の方が確実です。ルールを編集する間は 2 つ目の SSH セッションを開いたままにし、新しいルールで新規ログインできることを確認するまで閉じないでください。
ゾーンと、VPS ではデフォルトゾーンだけを使用する理由
ゾーンは、信頼レベルが設定された権限の名前付きセットです。firewalld は、受信パケットを必ず 1 つのゾーンに割り当てます。まず、各ゾーンの sources: リストとパケットの送信元アドレスを照合します。一致するものがなければ、受信インターフェースが所属するゾーンを使用します。インターフェースがどのゾーンにも所属していない場合、パケットはデフォルトゾーンに送られます。
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zonesネットワークインターフェースが 1 つだけの VPS では、最初の判定結果はほぼ常に public です。使用するゾーンもこれだけです。firewall-cmd に --zone= 引数を指定しない場合はデフォルトゾーンに対して動作します。そのため、このガイドの短いコマンドはすべてゾーン名なしで実行できます。
設定に時間を取られる典型的な失敗は、インターフェースが別のゾーンに所属している場合です。この場合、ルールは public に追加されますが、トラフィックは別のゾーンで処理されます。そのため、追加したルールは何も作用せず、警告も表示されません。--get-active-zones で所属先を確認できます。
public
interfaces: eth0インターフェースが別のゾーン名の下に表示される場合は、--zone= を使ってそのゾーンにルールを記述するか、インターフェースを移動します。
sudo firewall-cmd --permanent --zone=public --change-interface=eth0
sudo firewall-cmd --reloadRocky と AlmaLinux では NetworkManager がインターフェースを管理し、接続が起動するとゾーンの所属を再設定します。再起動で設定が元に戻らないように、NetworkManager 側にも設定してください。最初のコマンドの出力から接続名を取得します。接続名はデバイス名と異なることがほとんどです。
sudo nmcli connection show
sudo nmcli connection modify "System eth0" connection.zone public送信元アドレスによる照合はインターフェースによる照合より優先されます。これにより、1 つのアドレスに別のポリシーを適用できます。組み込みの trusted ゾーンはすべての通信を許可します。
sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.10/32
sudo firewall-cmd --reloadこのゾーンの使用には注意してください。そのアドレスからサーバー上のすべてのポートに接続できるようになり、非公開のつもりだったデータベースも対象になります。1 台のホストではなく 1 つのポートだけを許可する場合は、rich rule を使用してください。
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定義済みの名前がある場合は、それを使用してください。6 か月後に --list-all を読むときも内容が分かりやすくなります。また、Cockpit などのパッケージは独自のサービスファイルをインストールします。定義がないものには --add-port を使用してください。
注意すべき点があります。ssh サービスが意味するのは 22/tcp だけです。それ以外のポートは開きません。サーバーの SSH アクセスを強化するために SSH を別のポートへ移した場合、--add-service=ssh では実際に使用しているポートは開きません。
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reloadRHEL を再構築した環境では、もう 1 つ制限があります。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最初の 2 つのコマンドは、firewalld が認識している内容を表示します。3 つ目のコマンドは、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 -tlnp0.0.0.0:443 または *:443 と表示されるソケットは、すべてのアドレスからの接続を受け付けます。127.0.0.1:443 と表示されるソケットは loopback でのみ応答します。ファイアウォールのルールを追加しても、外部から到達できるようにはなりません。Linux のポートと待ち受けソケットでは、この違いについて詳しく説明しています。
ポートを再び閉じるにはどうすればよいですか?
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --reloadここでも --permanent のルールが適用されますが、この方向では影響がさらに大きくなります。サービスを実行時の設定からだけ削除すると、ポートはいったん閉じたように見えます。しかし、次回の reload または再起動時に、保存済みのファイルから再び開きます。実行した確認が成功するため、この穴に気付けません。
存在しないものを削除すると Warning: NOT_ENABLED: http と表示されますが、終了コードは 0 のままです。同じものを 2 回追加すると Warning: ALREADY_ENABLED: http と表示されます。どちらも安全です。名前のスペルミスは別です。Error: INVALID_SERVICE は、firewalld にその名前の定義がなく、何も変更されなかったことを意味します。
--list-all に cockpit と表示され、port 9090 の Cockpit Web コンソールを使用していない場合は、削除してください。開いているポートはすべて、パッチを適用し続ける必要があるサービスです。
1 つの送信元アドレスにポートを制限する
Rich rule は、通常のサービス名では意図した設定を表現できない場合に使用する詳細な形式です。SSH の接続元を 1 つのオフィスアドレスに制限するには 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ゾーンは権限の集合です。番号順に評価され、最初の一致で停止する一覧ではありません。Rich rule は、1 つのアドレスからの接続を許可します。ほかの接続を拒否するものではありません。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'特定のネットワークからのパケットを破棄し、その記録を残すには、action の前に log 要素を置きます。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 の接続元を 1 つのアドレスに限定する前に、そのアドレスが固定されていることを確認してください。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はすでにpubliczone の動作であり、--set-target=DROPはその無応答版ですsudo ufw logging onはsudo firewall-cmd --set-log-denied=allになります
1 つ、明確にしておくべき違いがあります。ufw は番号付きの一覧を管理するため、position 1 に rule を挿入できます。firewalld には rule number がないため、「この rule を先頭に置く」という指定はここでは意味を持ちません。firewalld の 2 つのエントリが矛盾しているように見える場合でも、set 内に deny がないため、広範な accept が優先されます。広範なエントリは手動で削除します。
Docker コンテナに到達できるのは、ファイアウォールが閉じているように見えるのに、なぜですか?
公開したコンテナポートは、ゾーンが制御するファイアウォールの経路を通らないためです。docker run -d -p 8080:80 nginx は、Docker に独自の NAT(network address translation)ルールと転送ルールを書き込ませます。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 firewalldenabled と active (running) が必要です。起動中でも有効化されていないファイアウォールは、最初の再起動までしかサーバーを保護しません。この確認は、新しい VPS の最初の 10 分間に行う項目に含め、SSH キーや更新と合わせて確認します。
Raw nftables コマンドと firewalld は併用できません。 firewalld は inet firewalld というテーブルを管理します。sudo nft flush ruleset はこのテーブルを削除するため、サーバーはすべての通信に対して開放された状態になります。それでも firewall-cmd --list-all は意図した設定を表示します。firewalld がカーネル内の実際の状態ではなく、自身が認識している設定を報告するためです。sudo firewall-cmd --reload でルールを再インストールできます。ルールは firewall-cmd で記述し、reload 後も復元されるようにします。
1 台のサーバーで 2 つのファイアウォール管理ツールを実行する場合。 firewalld と併用して ufw または iptables-services をインストールすると、互いを認識しない 2 つのプログラムがルールを書き込みます。どちらの設定が有効になるかは、最後に起動したサービスによって変わります。どちらか 1 つを選択してください。Rocky と AlmaLinux では、ディストリビューションのサポート対象である firewalld を使用します。
サーバーの前段にあるプロバイダーのファイアウォール。 多くの VPS パネルには、別のネットワークファイアウォールがあります。--list-all でポートが開いているのに外部からの接続に失敗する場合は、サーバー側を変更する前にパネルを確認してください。逆の場合もあります。パネル側のルールでポートを開いていても、firewalld がパケットを拒否すれば通信はできません。
sudo なしで firewall-cmd を実行する場合。 変更には root が必要です。root 権限がないと認証チェックで要求が拒否され、何も変更されません。そのため、コマンドが無視されたように見えます。
日常の大半の作業は 6 つのコマンドで対応できます。状態の確認には --list-all、ポートなどを開くには --permanent --add-service または --add-port、閉じるには --permanent --remove-service、保存済みファイルを適用するには --reload、一連の検証後には --runtime-to-permanent を使用します。ゾーンは public、フラグは --permanent です。確実な確認は、別のマシンからのみ行えます。
FAQ
firewalld のルールが再起動後に消えるのはなぜですか?
ルールが runtime configuration にのみ追加されていたためです。sudo firewall-cmd --add-service=http は直ちに適用されますが、保存された設定である /etc/firewalld/zones/public.xml が変更されていないため、次回の reload または boot で破棄されます。--permanent を追加してから sudo firewall-cmd --reload を実行してください。手動で追加済みのルールを保持するには、sudo firewall-cmd --runtime-to-permanent を実行します。これにより、現在有効なルール一式が保存ファイルにコピーされます。
--permanent を付けてルールを追加しても何も変わらないのはなぜですか?
--permanent はファイルに書き込みますが、実行中の firewall には変更を適用しないためです。sudo firewall-cmd --reload が保存された設定を kernel に読み込むまで、ポートは閉じたままです。sudo firewall-cmd --list-services と sudo firewall-cmd --permanent --list-services を比較してください。保存側の一覧にあり、現在有効な一覧にないエントリがあれば、不足しているのは reload です。
--add-service と --add-port のどちらを使うべきですか?
実行するサービスに名前がある場合は --add-service を使います。意図を明確にでき、sudo firewall-cmd --info-service=https でその名前が対象とするポートを正確に確認できます。サービスを定義する名前がない場合や、標準以外のポートで待ち受ける場合は --add-port を使います。ssh サービスが対象とするのは 22/tcp だけです。そのため、SSH を 2222 に移した場合は、--add-port=2222/tcp と、そのポート用の SELinux ラベルが必要です。
firewalld でポートが閉じていると表示されるのに、Docker コンテナへ到達できるのはなぜですか?
公開されたポートは Docker 独自の NAT ルールによって書き換えられ、コンテナへ転送されます。そのため、パケットは host に配信されません。zone のサービス一覧とポート一覧が対象とするのは、host に配信されたパケットだけです。--list-all に何も表示されなくても、コンテナはインターネットからの通信に応答します。docker run -d -p 127.0.0.1:8080:80 nginx を使って loopback にのみ公開し、その前段にリバースプロキシを配置してください。
firewalld の代わりに Rocky Linux に ufw をインストールできますか?
1 台のサーバーで 2 つの firewall manager を使うと、互いを認識せずにルールを書き込みます。どのルールセットが残るかは、どちらのサービスが最後に起動したかによって決まります。firewalld は Rocky Linux と AlmaLinux でサポートされているツールです。すでにインストールされており、ufw と同じ nftables backend を操作します。default zone と --permanent flag を一度覚えれば、このツール全体を扱えます。