深夜2時、chmod 777でもNginxが403エラーを返す原因
深夜2時、PagerDutyのアラートが鳴り響きます。オンコール担当者がAlmaLinux 9クラスタへホットフィックスをデプロイした直後、Nginxがすべての静的ファイル(CSS、JS、PNG画像)に対して突如403 Forbiddenを返し始めました。ls -lコマンドでPOSIXパーミッションを確認しても、オーナー設定は正常なnginx:nginxのままです。試しにWebルートディレクトリに対してchmod -R 777を実行してみても、ブラウザには無情にも403エラーが返され続けます。
本番運用の経験が浅いエンジニアなら、つい手軽にsetenforce 0を実行してしまうかもしれません。確かにこれですぐにWebサイトは復旧します。しかしその対処は、カーネルレベルのセキュリティの扉を完全に開放してしまっているのと同じです。
SELinuxのアクセス拒否に対する3つの対処アプローチ
RHEL/AlmaLinux環境において、SELinuxがプロセスによるファイルやネットワークポートへのアクセスをブロックした場合、システム管理者は通常次の3つの選択肢に直面します。
- アプローチ1:SELinuxを完全に無効化する(PermissiveまたはDisabled):
setenforce 0を実行してpermissiveモードに切り替えるか、/etc/selinux/configをSELINUX=disabledに書き換えてOSを再起動します。この方法はサーバーの防御壁を全面的に解除することになります。 - アプローチ2:Security Contextの適正化とBooleanの設定:
enforcingモードを維持したまま運用します。AVC Deniedログを正確に読み解き、semanage fcontextでファイルに適切なラベルを再付与し、必要なBooleanフラグを有効化します。 - アプローチ3:audit2allowによるカスタムポリシーの生成:拒否されたログを収集し、
audit2allow -Mを使用してバイナリモジュール(.pp)へとコンパイルし、カーネルに直接ロードします。
各手法のリスクとトレードオフの比較
アプローチ1:DisableまたはPermissive
- メリット:迅速かつシンプルです。わずか5秒で作業が完了し、即座にWebサイトが復旧します。
- デメリット:強制アクセス制御(MAC: Mandatory Access Control)の仕組みが完全に無効化されます。仮にWebアプリケーションにRCE(リモートコード実行)の脆弱性が存在した場合、
nginxプロセスの権限を奪取した攻撃者は、/etc/shadowやサーバー上の他ユーザーのSSHキーを直接読み取ることが可能になります。
アプローチ2:適切なContextラベルの付与とBooleanの有効化
- メリット:根本的でセキュアな解決策です。最小特権の原則(Least Privilege)に厳密に準拠し、Linux本来の強力なプロセス分離機能をそのまま維持できます。
- デメリット:ログの解析や適切なコマンドフラグの調査に、3〜5分程度の追加時間が必要です。
アプローチ3:audit2allowによるモジュールの自動生成
- メリット:Red Hat標準のtargeted policyが用意されていない独自の特殊なソフトウェアを実行する際に役立ちます。
- デメリット:セキュリティホールを生み出すリスクがあります。コマンド実行時にアプリケーションがすでに攻撃を受けていた場合、その攻撃挙動まで正規のカーネルポリシーとして承認してしまうことになります。
推奨方針:本番環境ではEnforcingを維持する
本番環境において、Enforcingモードを維持することは極めて重要な鉄則です。ログの読み方を把握し、セキュリティラベルの本質を理解していれば、SELinuxの適切な設定にはほんの数分しかかかりません。
根本原因を診断・解決する5ステップの手順
1. audit.logからAVC Deniedログを調査する
カーネルは操作をブロックするたびに、AVC(Access Vector Cache)レコードを/var/log/audit/audit.logに記録します。次のコマンドで直近に発生したイベントを素早くフィルタリングできます。
grep "type=AVC" /var/log/audit/audit.log | tail -n 5
サーバーでsystemd-journaldを使用している場合は、直接次のようにログを絞り込めます。
journalctl -t setroubleshoot --since "10 minutes ago"
典型的な拒否ログは以下のようになります。
type=AVC msg=audit(1697801060.450:892): avc: denied { read } for pid=4812 comm="nginx" name="index.html" dev="sda3" ino=1358249 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
上記のログを確認すると、ソースラベル(scontext)がhttpd_tであるプロセスnginxが、ターゲットラベル(tcontext)がuser_home_tとなっているファイルindex.htmlを読み込もうとして拒否されていることがわかります。このエラーは通常、/home/deploy/などのディレクトリから/var/www/htmlへmvコマンドでファイルを移動した際に発生します。移動先ディレクトリのラベルを継承せず、元のラベルがそのまま保持されてしまうためです。
2. ファイルのSecurity Contextを確認する
すべてのプロセス、ファイル、ポートにはuser:role:type:levelという形式の識別ラベルが割り当てられています。その中でも、typeがType Enforcementの仕組みを通じてアクセス権限を決定づけます。
lsコマンドに-Zオプションを付与してラベルを確認します。
ls -lZ /var/www/html
実行結果の例:
-rw-r--r--. 1 nginx nginx unconfined_u:object_r:user_home_t:s0 index.html
デフォルトのルールでは、Nginxはtypeがhttpd_sys_content_tのファイルの読み込み、またはhttpd_sys_rw_content_tのファイルへの書き込みのみが許可されています。そのため、上記のuser_home_tラベルが付いたファイルへのアクセスが拒否されるのは完全に正常な動作です。
3. Security Contextを恒久的に再設定する
chconコマンドは使用しないでください。このコマンドは一時的な変更に過ぎず、システムの再ラベリングやバックアップのリストアを実行するとリセットされてしまいます。必ずsemanageでポリシーデータベースにルールを登録し、その後にrestoreconを実行します。
# 1. SELinuxデータベースにルールを登録
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/html(/.*)?"
# 2. ディレクトリ全体に再帰的にラベルを適用
sudo restorecon -Rv /var/www/html
再度ls -lZ /var/www/htmlを実行します。typeがhttpd_sys_content_tに更新されていることが確認できます。Webページをリロードすると、Nginxを再起動することなく403エラーが即座に解消されます。
4. Nginxをリバースプロキシとして使用する場合のBoolean設定
NginxをNode.js(ポート3000)やPHP-FPMソケットなどのバックエンドへのリバースプロキシとして利用する場合、今度は502 Bad Gatewayエラーが発生することがあります。SELinuxはデフォルトで、Webサーバーが外部へのネットワーク接続を開始することを禁止しているためです。
Webサーバーに関連するBooleanフラグの状態を確認します。
getsebool -a | grep httpd
ネットワーク接続を許可するフラグを有効化し、-Pオプションを付与して再起動後も設定が保持されるようにします。
# Nginxがバックエンド(Node.js、Tomcat、Gunicornなど)へリクエストをプロキシすることを許可
sudo setsebool -P httpd_can_network_connect 1
# Nginxがリモートデータベースサーバーへ直接接続することを許可(必要な場合)
sudo setsebool -P httpd_can_network_connect_db 1
5. sealertを活用して自動修正の提案を取得する
ログの読み取りが難しい複雑なトラブルに遭遇した場合は、Red Hatが提供する診断ツールセットを導入するのが有効です。
sudo dnf install -y setroubleshoot-server
sudo sealert -a /var/log/audit/audit.log
このツールはすべてのdeniedブロックをスキャンし、原因を平易な英語で分析した上で、トラブルシューティングに必要な正確なsemanageやsetseboolコマンドを提示してくれます。あとはそれをコピー&ペーストするだけで迅速に対処が可能です。

