Nginxの「Internal Redirection Cycle」(try_filesループ)の解決方法

intermediate Nginx2026-07-12

問題の概要

SPA(シングルページアプリケーション)やLaravelプロジェクトのデプロイを終えたばかりだとします。すべてが完璧に見えますが、ページをリロードした途端に「500 Internal Server Error」が発生します。Nginxのエラーログを確認すると、次のような特有の悩ましいエラーが見つかるはずです:

rewrite or internal redirection cycle while processing "/index.html"

これはNginxがループに陥っているために発生します。Nginxはファイルを探し、見つからないとフォールバック(代替手段)にジャンプしますが、そのフォールバックが全く同じ検索ロジックをトリガーしてしまいます。Nginxには内部リダイレクトの回数に10回という上限があります。11回目の試行に達すると、CPUの過負荷を防ぐためにリクエストを強制終了します。

環境

  • OS: モダンなLinuxディストリビューション (Ubuntu 22.04+, Debian, RHEL)
  • ソフトウェア: Nginx (全バージョン)
  • 一般的なシナリオ: React/Vueでのクライアントサイド・ルーティング、PHPフレームワークでのindex.phpルーティング、またはカスタム404ハンドラー。

ループが発生する仕組み

通常、問題は楽観的すぎる try_files ディレクティブから始まります。ViteベースのReactアプリでよく見られる(しかし欠陥のある)設定を見てみましょう:

server {
    listen 80;
    root /var/www/my-app/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

これはNginxに対し、まずリテラルファイルを確認し、次にディレクトリを確認するように指示しています。両方が見つからない場合は、index.html を提供します。

罠は単純です: もし /dist フォルダに index.html が実際に存在しない場合(ビルドの失敗などが原因)、Nginxはそこで止まりません。/index.html へリダイレクトし、それが再び location / ブロックをトリガーします。検索して失敗し、2回目の /index.html へのリダイレクトが発生します。これがNginxが10サイクルの制限に達して諦めるまで繰り返されます。

ステップバイステップのデバッグ

1. エラーログの確認

ログをリアルタイムで監視して、どのパスが失敗しているかを特定します。ブラウザをリロードしながら、次のコマンドを実行してください:

tail -f /var/log/nginx/error.log

エラー内のパスに注意を払ってください。もし while processing "/index.php" と表示されているなら、Nginxは index.php がチェーン内の欠落しているリンクであることを伝えています。

2. ファイルパスと権限の確認

フォールバックファイルが、想定した場所に実際に存在するか確認します。ルートパスに対して ls を実行してみましょう:

ls -la /var/www/my-app/dist/index.html

ファイルが存在していても、Nginxがその読み取りをブロックされている可能性があります。namei を使用して、権限チェーン全体を確認します。/ からファイルに至るまでのすべてのディレクトリが、www-data ユーザーによって実行可能(+x)である必要があります:

namei -om /var/www/my-app/dist/index.html

3. 安全のために名前付きロケーションを使用する

名前付きロケーション(Named locations)は、よりクリーンな代替案です。ファイル確認ロジックと実際のフォールバック実行を分離し、偶発的な再帰を防ぎます。これはSPAを扱う際の業界標準の方法です。

SPA (React/Vue/Vite) の場合:

location / {
    try_files $uri $uri/ @fallback;
}

location @fallback {
    rewrite ^ /index.html break;
}

PHP (Laravel/WordPress) の場合:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

PHPの設定では、多くの場合 .php ブロック内でループが発生します。サイクルを止めるには、PHPハンドラー内の try_files ディレクティブに =404 を追加します。これにより、index.php が存在しない場合に、Nginxはループせずにクリーンな404を返すようになります。

location ~ \.php$ {
    try_files $uri =404;
    fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

最終的な解決策:堅牢な設定

シンプルな try_files 設定を好む場合は、最後のパラメータが新しい検索をトリガーしないようにする必要があります。以下は、モダンなウェブアプリ向けの検証済み設定です:

server {
    listen 80;
    root /var/www/html;
    index index.html;

    location / {
        # ファイル、次にディレクトリを試行します。
        # 両方が失敗した場合は、リクエストを index.html に送信します。
        try_files $uri $uri/ /index.html;
    }

    # ループの可能性を断つために、index.html を明示的に処理します
    location = /index.html {
        # 'internal' ディレクティブは、ファイルが存在しない場合に
        # 外部ユーザーがこのロケーションを直接呼び出すのを防ぎます。
        internal;
    }
}

検証

作業を終える前に、必ずNginxの構文を検証してください。セミコロンが1つ欠けているだけで、サイト全体がダウンする可能性があります。

sudo nginx -t

もし syntax is ok と表示されたら、変更を適用します:

sudo systemctl reload nginx

最後に、curl を使って存在しないルートをテストします。500 エラーの代わりに 200 OK(SPAのフォールバック)または 404 が返ってくれば、ループの解消に成功しています:

curl -I http://localhost/this-route-does-not-exist

学んだ教訓

  • フォールバックはリダイレクトである: try_files の最後の項目は内部リダイレクトです。そのファイルが欠落していると、検索がやり直しになります。
  • ルートのタイポは致命的: root パスを再確認してください。Nginxが /var/www/html を探しているのに、ファイルが /var/www/dist にある場合、永久にループします。
  • 名前付きロケーションの方が安全: 複雑なルーティングでは @name 構文を使用して、ロジックをクリーンに保ちましょう。
  • 権限が重要: Nginxは読み取れないファイルを提供することはできません。www-data がファイルの所有権を持っていることを確認してください。

Related Error Notes