はじめに (対象読者・この記事でわかること)

この記事は、Apache HTTP Server + Tomcat の構成で Web アプリケーションを運用していて、「リバースプロキシ(AJPProxy)を使ってアプリケーションのコンテキストパスを / にしたい!」と考えている開発者・運用者を対象としています。

具体的には、Tomcat で動いているアプリを https://example.com/ で直接表示したいけれど、CSS や JavaScript の相対パスが 404 になり、スタイルやスクリプトが効かなくなってしまう問題を抱えている方向けです。

この記事を読むと、なぜ相対パスが解決できなくなるのか、その根本原因と、Apache の mod_rewrite・mod_substitute を使って安全に回避する方法を実践的に理解できます。

前提知識

  • Apache HTTP Server の基本的な設定(VirtualHost や ProxyPass)が読める
  • Tomcat のコンテキストパス(Context Path)の意味を知っている
  • HTML の相対パス(css/style.css../js/app.js など)がどのように解決されるかをある程度理解している
  • ターミナルで設定ファイルを編集し、Apache を再起動できる環境にある

AJPProxy でルートコンテキストにすると何が起きるのか

Java の Web アプリケーションは通常、WAR ファイル名がコンテキストパスになります。
例えば myapp.war をデプロイすると https://example.com/myapp/ でアクセスできます。

しかし、ブラウザで https://example.com/ だけでアクセスできるようにしたいケースがあります。
そのために Apache の AJPProxy で以下のように設定することが多いです。

Apache
<VirtualHost *:443> ServerName example.com ProxyPass / ajp://localhost:8009/myapp/ ProxyPassReverse / ajp://localhost:8009/myapp/ </VirtualHost>

この設定により、ブラウザが / をリクエストすると、Tomcat の /myapp が応答する、いわば「ルートコンテキスト化」が実現します。

相対パスが 404 になる理由

Tomcat 上ではコンテキストパスが /myapp なので、JSP や HTML は次のように相対パスでリソースを読みに行きます。

Html
<link rel="stylesheet" href="css/style.css"> <script src="js/app.js"></script>

ブラウザは現在の URL を基準にしてリソースを解決するため、
https://example.com/css/style.css をリクエストします。
しかし、Tomcat 側の実際のファイルは /myapp/css/style.css にあるため、404 Not Found が返されます。

これが「CSS/JS の相対パスが読み込めない」現象の正体です。

具体的な手順と回避策

以降、Apache で 2 つの方針を組み合わせて解決します。

  1. リバースプロキシパスを補正する(mod_rewrite)
  2. レスポンス内の相対パスを書き換える(mod_substitute)

ステップ1:mod_rewrite でプロキシパスを補正

まず、/css /js /img などの静的リソースに対してのみ、Tomcat のコンテキストパスを付与してプロキシします。

Apache
<VirtualHost *:443> ServerName example.com # 静的リソースは /myapp 以下に転送 RewriteEngine On RewriteRule ^/(css|js|img)/(.*)$ ajp://localhost:8009/myapp/$1/$2 [P] # その他はルートに転送 ProxyPass / ajp://localhost:8009/myapp/ nocanon ProxyPassReverse / ajp://localhost:8009/myapp/ </VirtualHost>

これで https://example.com/css/style.css は Tomcat の /myapp/css/style.css として解決されます。

ステップ2:mod_substitute で HTML レスポンスを書き換える

JSP 内で相対パスを出力している場合、ブラウザが解決する前に Apache 側で絶対パスに置換してしまいます。

Apache
LoadModule substitute_module modules/mod_substitute.so <VirtualHost *:443> ... # HTML レスポンス内の相対パスを絶対パスに Substitute "s|href=\"css/|href=\"/myapp/css/|i" Substitute "s|src=\"js/|src=\"/myapp/js/|i" # 必要に応じて追加 </VirtualHost>

Substitute ディレクティブはレスポンスボディに対して正規表現置換を行うため、
クライアントには /myapp/css/style.css という絶対パスが返されます。
ブラウザは https://example.com/myapp/css/style.css をリクエストし、ステップ 1 の RewriteRule で正しく転送されます。

ハマった点とエラー解決

  • mod_substitute が効かない
    レスポンスが gzip 圧縮されていると置換できません。
    対策として、Tomcat 側の圧縮を無効化するか、Apache で SetOutputFilter INFLATE;substitute;DEFLATE とデコンプレス→置換→再圧縮のフィルタチェインを構成します。

  • AJP エラー「No protocol route」
    RewriteRule ... [P]ProxyPass を併用するとき、ループや優先順位で変わる動作があります。
    必ず RewriteRule を先に記述し、ProxyPassnocanon オプションを付けてスラッシュエンコードの再計算を防ぎます。

  • キャッシュで書き換えが反映されない
    ブラウザや CDN で CSS/JS がキャッシュされていると、即座に反映されません。
    開発中は Cache-Control: no-cache を付けるか、ファイル名にバージョニング(style.css?v=1.0.1)を使いましょう。

解決策まとめ

  1. Apache で mod_rewrite を使い、静的リソースのパスにだけ /myapp を付けて AJP プロキシ
  2. mod_substitute で HTML レスポンス内の相対パスを事前に絶対パスに書き換え
  3. 圧縮やキャッシュに注意しつつ、設定をリロードすれば即座に反映

まとめ

本記事では、AJPProxy で Tomcat アプリをルートコンテキスト化した際に起きる「CSS/JS の相対パス 404 問題」のメカニズムと、Apache の mod_rewrite・mod_substitute を活用した回避策を解説しました。

  • 相対パスが 404 になるのは「ブラウザの解決基準」と「Tomcat 上の実ファイルパス」がずれるため
  • RewriteRule で静的リソースにだけコンテキストパスを付与
  • Substitute で HTML レスポンスを前方置換し、ブラウザに絶対パスを認識させる

これにより、WAR ファイル名を変更せずに、ブラウザではシンプルな https://example.com/ でアプリケーションを公開できます。
次回は、同様の問題を nginx や Traefik で解決する方法や、Spring Boot の server.forward-headers-strategy=NATIVE を使ったアプローチについて紹介する予定です。

参考資料