Nginxプロキシでエラー画面を統合する

Nginxプロキシでエラー画面を統合する

1. 問題の定義:バラバラなエラー画面とセキュリティリスク

エンタープライズおよびマイクロサービスのWebアーキテクチャでエラーが発生すると、エラーが発生した層に応じて表示される画面がバラバラになる問題が発生します。最前線にNginxリバースプロキシがあり、その後段にTomcat内蔵のSpring Bootアプリケーションが接続された環境では、次のような分断された画面が表示されます。

  • Spring Boot Whitelabel Error Page: HTTPステータスコード、タイムスタンプ、エラーメッセージが生のままブラウザに表示されます。

  • Apache Tomcatのデフォルトエラーページ: サーブレットコンテナレベルで404/500エラーが発生すると、特徴的なグレースケールのTomcat画面が表示されます。

  • Nginxのデフォルトエラーページ: バックエンドアプリケーションがダウンしたりタイムアウトしたりすると、無機質な502 Bad Gateway / 504 Gateway Time-outのテキスト画面が表示されます。

このような断片化はユーザー体験(UX)を損なうだけでなく、セキュリティ脆弱性(CWE-209:情報漏えい)を引き起こします。Tomcatのバージョン情報やフレームワークのStack Traceが露出する「サーバーフィンガープリンティング(Server Fingerprinting)」により、攻撃者にシステムの脆弱性分析の手がかりを与えることになります。

image1.pngimage2.png

Spring Bootエラーページ

image3.png

Apache Tomcatエラーページ

image4.png

Nginxエラーページ

2. 技術的な限界:Spring Boot内部処理の限界

Spring Bootアプリケーション内部の@ExceptionHandlerやErrorControllerは、アプリケーション(JVM/Tomcat)プロセスが正常に稼働しており、レスポンスを生成できる状態でのみ動作します。

実際の運用環境における致命的な障害は、アプリケーションコードが実行されることさえない区間で発生します。

  • JVM OOM(Out of Memory)クラッシュ:ヒープメモリが枯渇してプロセスが強制終了された場合(502 Bad Gateway)

  • Tomcat Thread Pool / DB Connection Poolの枯渇: 大量のトラフィックによりTomcatが新しいコネクションを確立できない場合(504 Gateway Time-out)

  • 無停止デプロイのローリングアップデートによる瞬断: 新しいコンテナが起動する前の一瞬に流入したリクエスト

このようなインフラ由来の障害状況ではSpring Bootのコードを実行できないため、Nginxの生のエラー画面が露出します。真の高可用性エラー制御を実現するには、トラフィックの最初の入口であるNginx層でエラーハンドリングを一元化する必要があります。

3. 解決策の設計:Nginx proxy_intercept_errorsに基づく一元制御

Nginxを最前線のエラーコントローラーへ切り替える中核メカニズムは、proxy_intercept_errorsディレクティブです。このディレクティブを有効にすると、NginxはUpstream(Tomcat/Spring Boot)が返すHTTPレスポンスコードが300以上の場合、レスポンス本文をインターセプトし、あらかじめ定義されたerror_pageパスへ内部的に再ルーティングします。

  • セキュリティ分離: バックエンドでどのような例外が発生してもレスポンスボディを破棄し、Nginxのサニタイズされた静的ファイルのみを配信することで、内部情報の漏えいを防止します。

  • インフラレベルのフォールバック: Spring Bootの稼働状況にかかわらず、一貫したサービス案内画面を提供します。

  • 直接アクセスの遮断: internalディレクティブにより、ユーザーがブラウザのアドレスバーに/error/500.htmlを直接入力してアクセスすることを防止します。

4. 実践的なプロダクションnginx.conf設定例

TomcatおよびSpring Bootのエラーをインターセプトし、カスタムエラー画面へルーティングするnginx.confの主要構成例です。

# 1. [Content Negotiation] 요청 헤더(Accept) 기반 응답 포맷 판별
#   React 비동기 API 요청(JSON)과 일반 웹 브라우징(HTML)을 구분합니다.
map $http_accept $error_response_type {
  default "html";
  ~*application/json "json";
  ~*text/json "json";
}

server {
    listen 80;
    server_name service.example.com;

    # 2. [핵심] 뒷단 WAS(Tomcat/Spring Boot)의 3xx/4xx/5xx 에러를 가로챕니다.
    proxy_intercept_errors on;

    # 3. HTTP 상태 코드별 내부 에러 디스패처로 라우팅
    error_page 404 = /handle_error_404;
    error_page 500 = /handle_error_500;
    error_page 502 = /handle_error_502;
    error_page 504 = /handle_error_504;

    # 기본 비즈니스 프록시 패스
    location / {
        proxy_pass http://backend_app;
        # WAS 장애 시 빠른 Nginx 에러 핸들러 전환을 위한 타임아웃
        proxy_connect_timeout 3s;
        proxy_read_timeout 10s;
    }

    # 4. [내부 디스패처] 헤더에 따라 JSON vs HTML 분기 반환
    #   internal 지시어로 외부 사용자의 직접 URL 호출을 차단합니다.
    location = /handle_error_500 {
        internal;
        if ($error_response_type = "json") {
            default_type application/json;
            return 500 '{"status": 500, "code": "INTERNAL_SERVER_ERROR", "message": "서버 오류가 발생했습니다."}';
        }
        rewrite ^ /error/500.html break;
    }

    # 5. [WAS 다운 대응] Spring Boot 프로세스 다운(OOM 등) 시 502 처리
    location = /handle_error_502 {
        internal;
        if ($error_response_type = "json") {
            default_type application/json;
            return 502 '{"status": 502, "code": "SERVICE_UNAVAILABLE", "message": "현재 서비스 점검 중입니다."}';
        }
        rewrite ^ /error/502.html break;
    }

    # 6. [정적 파일 격리] 커스텀 HTML 에러 페이지 서빙 (직접 접근 불가)
    location /error/ {
        internal;
        alias /usr/share/nginx/html/error/;
        add_header Cache-Control "no-store, no-cache, must-revalidate";
    }
}

Nginx.confのサンプルコード

5. 発展的な設計:React連携時に発生する白い画面(white Screen)問題と解決策

proxy_intercept_errors on;を単純に適用すると、通常のWebページは正常に動作しますが、React SPA環境では致命的なランタイムクラッシュが発生します。

  • 問題の状況(SyntaxErrorと白い画面):
    Reactでaxios/fetchを使ってAPIを呼び出している際に、バックエンドが500エラーを返したり、Tomcatがダウン(502)したりすると、Nginxはerror_pageに指定されたHTMLドキュメントを返します。フロントエンドはこれをJSONとしてパースしようとしてSyntaxError: Unexpected token '<'を発生させ、画面全体が真っ白になって停止する(white Screen)現象が起こります。

  • 解決方法(Nginx Content Negotiation):
    NginxのmapモジュールでリクエストヘッダーのAccept値を判別し、通常のブラウジング(text/html)にはHTML画面を、Reactの非同期APIリクエスト(application/json)には標準化されたJSONエラースキーマを即座に返すように分岐処理します。

  • 改善効果:
    Spring Bootが完全にダウンしていても、ReactのグローバルAxiosインターセプターが標準化されたJSONレスポンスを受け取り、「サーバー点検中」モーダルやトースト通知を安全に表示できるようになり、完全なインフラレベルのFallbackを実現できます。

6. 結論:層の分離が生み出すインフラの信頼性

Nginxを活用したプロキシ層のエラー制御は、単なるデザインのカスタマイズを超え、インフラ(Nginx)とアプリケーション(Tomcat/Spring Boot)の関心事を分離(Separation of Concerns)する中核アーキテクチャです。

アプリケーションは純粋なビジネスロジックとドメイン例外に集中し、インフラ障害やダウンタイムは最前線のプロキシがインターセプトして標準化された形式で応答する構造を構築することで、サービスの安定性とセキュリティを同時に確保できます。

TK

Site footer