エラーの内容
Nginxのエラーログに以下のような内容が表示されます:
2024/01/15 10:23:41 [crit] 12345#0: *1 connect() to 127.0.0.1:8080 failed (13: Permission denied) while connecting to upstream, client: 203.0.113.1, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8080/", host: "example.com"
Nginxはブラウザに502 Bad Gatewayを返します。NginxやバックエンドサービスをRestartしても何も変わりません。バックエンドは実際には動いており、curl localhost:8080は正常にレスポンスを返します。これはSELinuxによる典型的なブロックです。
根本原因
SELinuxは標準のLinuxパーミッションの上位に強制アクセス制御を適用します。RHELベースのシステムでは、デフォルトでEnforcingモードで動作しており、Nginxの動作に対しても厳格なルールが適用されます。
NginxがリバースプロキシとしてI動作する場合、アウトバウンドのネットワーク接続を行うための明示的なSELinuxの許可が必要です。その許可がなければ、rootとして実行していてもファイルパーミッションが正常に見えても、カーネルはEACCES (13: Permission denied)を返します。
このエラーが発生しやすい状況:
- 非標準ポートでNode.js、Python、Ruby、Javaアプリへプロキシする場合
- Unixソケット経由でPHP-FPMへプロキシする場合(
/run/php-fpm/www.sock) - 既存のセットアップを新しいRHEL/CentOS/Rockyサーバーへ移行した場合
- バックエンドのポートを80や443(デフォルトで許可)から8080、3000、5000などに変更した場合
SELinuxが原因か確認する
推測せず、まず以下を実行してください:
# SELinuxの状態を確認
getenforce
# 出力されるべき値: Enforcing
# 拒否のauditログを確認
sudo ausearch -m avc -ts recent | grep nginx
# またはauditログを直接スキャン
sudo grep 'nginx' /var/log/audit/audit.log | grep denied
拒否のログは以下のようになります:
type=AVC msg=audit(...): avc: denied { name_connect } for pid=12345 comm="nginx" dest=8080 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket permissive=0
denied { name_connect }という記述が決定的な証拠です。SELinuxがNginxのアップストリームポートへのアクセスをブロックしています。
修正方法1:httpd_can_network_connectを有効にする(推奨)
ほとんどの場合、このboolean一つで解決します。Nginxが動作するhttpd_tドメインが任意のポートへのアウトバウンドTCP接続を行えるようになります:
# 永続的に有効化(再起動後も維持される)
sudo setsebool -P httpd_can_network_connect 1
# 確認
getsebool httpd_can_network_connect
-Pフラグは重要です。省略すると、次の再起動時に設定が元に戻ります。
その後、Nginxをリロードして確認してください:
sudo nginx -t && sudo systemctl reload nginx
修正方法2:semanageで特定のポートを許可する
全アウトバウンド接続を開放するのではなく、特定のポートにラベルを付けることでNginxからのアクセスを許可できます。セキュリティの観点からより厳密な方法です。
# semanageがない場合はインストール
sudo dnf install -y policycoreutils-python-utils
# バックエンドポート(例:3000)をhttp_port_tとしてラベル付け
sudo semanage port -a -t http_port_t -p tcp 3000
# 別のラベルが既に付いている場合は -m で変更:
sudo semanage port -m -t http_port_t -p tcp 3000
# 確認
sudo semanage port -l | grep http_port_t
デフォルトでhttp_port_tとしてラベル付けされているポート:80、443、8008、8009、8080、8443。バックエンドがこれらのポートを使用しているにもかかわらずエラーが発生する場合は、修正方法1を試してください。問題は別の箇所にあります。
修正方法3:Unixソケット(PHP-FPM、Gunicornなど)
TCPポートではなくUnixソケットへプロキシしている場合:
upstream backend {
server unix:/run/myapp/app.sock;
}
ソケットファイルには正しいSELinuxコンテキストが必要です。現在のコンテキストを確認します:
ls -Z /run/myapp/app.sock
httpd_var_run_tまたはhttpd_sock_tが表示されるべきです。var_run_tや他の値が表示される場合は、再ラベル付けを行います:
# 一時的な再ラベル付け
sudo chcon -t httpd_sock_t /run/myapp/app.sock
# 永続的な設定 — 再起動後も維持されるようディレクトリにコンテキストを設定
sudo semanage fcontext -a -t httpd_sock_t "/run/myapp(/.*)?"
sudo restorecon -Rv /run/myapp/
PHP-FPMの場合は、以下のbooleanも設定してください:
sudo setsebool -P httpd_can_network_connect 1
修正を確認する
# Nginxをリロード
sudo systemctl reload nginx
# 新しいAVC拒否がないか確認(空であるべき)
sudo ausearch -m avc -ts recent | grep nginx
# エンドポイントにアクセス
curl -I http://example.com/
# エラーログをリアルタイムで監視
sudo tail -f /var/log/nginx/error.log
502エラーがなく、auditログにAVC拒否もない状態であれば完了です。
やってはいけないこと
Stack Overflowで最もよく見かける「修正方法」はこれです:
# 本番環境でこれをやってはいけない
sudo setenforce 0
確かに動作します。だからこそ多くの人が実行してしまいます。しかしこれはNginxだけでなく、システム全体のSELinux強制を無効にします。他のすべてのサービスも保護を失います。代わりに上記の個別の修正方法を使用してください。
Permissiveモードは原因の確認には有効です。確認後は必ず強制モードに戻してください:
# 一時的に無効化してSELinuxが原因か確認
sudo setenforce 0
curl -I http://example.com/ # 動作する場合、原因は確実にSELinux
# 強制モードに戻して正しい修正を適用
sudo setenforce 1
sudo setsebool -P httpd_can_network_connect 1
補足情報
Unixソケットのディレクトリパーミッション — Nginxとアプリケーションがソケットにアクセスできつつも過度に開放しないための適切なモード設定に悩んでいる場合は、ToolCraftのUnix Permissions Calculatorを使うと、chmodの値を視覚的に確認できます。8進数の計算は不要です。
より詳細なSELinuxのデバッグには、audit2whyとaudit2allow(どちらもpolicycoreutils-python-utilsパッケージに含まれる)を使用すると、AVC拒否の内容を平易な言葉で確認できます。audit2allowは、標準のbooleanでは対応できないエッジケース向けのカスタムポリシーモジュールも生成できます:
# 拒否された内容とその理由をわかりやすく説明
sudo ausearch -m avc -ts recent | audit2why
# 特殊なケース向けのカスタムポリシーモジュールを作成
sudo ausearch -m avc -ts recent | audit2allow -M mynginx
sudo semodule -i mynginx.pp

