Apache ProxyPassReverseでリダイレクト問題を解決!設定とトラブルシューティング

2025-05-27

Apache HTTP Serverのmod_proxyモジュールは、リバースプロキシを構築する際に非常に重要な機能を提供します。その中でもProxyPassReverseディレクティブは、特にバックエンドサーバーからの応答を適切に処理するために不可欠です。

リバースプロキシとは?

まず、ProxyPassReverseが使われる「リバースプロキシ」について簡単に説明します。

リバースプロキシとは、クライアントからのリクエストを代わりに受け取り、それを内部の別のサーバー(バックエンドサーバー、またはオリジンサーバーと呼びます)に転送し、バックエンドサーバーからの応答をクライアントに返すサーバーのことです。

クライアントはリバースプロキシに対してリクエストを送信するため、バックエンドサーバーの存在を直接知る必要はありません。これにより、セキュリティの向上、負荷分散、SSL終端、キャッシュなどの機能を提供できます。


  • Apacheがその応答をクライアントに返します。
  • アプリケーションサーバーが応答を生成し、Apacheに返します。
  • Apacheはリクエストを内部のアプリケーションサーバー(例: http://localhost:8080/myapp/)に転送します。
  • Apacheリバースプロキシがこれを受け取ります。
  • クライアントが http://www.example.com/app/ にアクセスします。

ProxyPass ディレクティブ

ProxyPassReverseを理解する前に、ProxyPassディレクティブについて触れておきましょう。

ProxyPassは、特定のリクエストパスをバックエンドサーバーのURLにマッピングするために使用されます。

構文

ProxyPass [リクエストパス] [バックエンドURL]


ProxyPass /app/ http://localhost:8080/myapp/

この設定は、「http://www.example.com/app/ へのリクエストは、http://localhost:8080/myapp/ に転送する」という意味になります。

ProxyPassReverse ディレクティブの役割

ProxyPassReverseは、ProxyPassと組み合わせて使用されます。その主な役割は、バックエンドサーバーからのHTTP応答ヘッダー(特にLocation:ヘッダー)を書き換えることです。

バックエンドサーバーがリダイレクト(例: 302 Found)を返す場合、そのLocation:ヘッダーにはバックエンドサーバー自身のURL(例: http://localhost:8080/myapp/login)が含まれていることがあります。しかし、クライアントはバックエンドサーバーの存在を知らないため、このURLに直接アクセスすることはできません。

ProxyPassReverseは、このような場合にLocation:ヘッダー内のURLを、クライアントがアクセスしているリバースプロキシの公開URL(例: http://www.example.com/app/login)に自動的に書き換えます。これにより、クライアントは適切にリダイレクトされ、バックエンドサーバーの内部アドレスを知ることなく、サービスを継続して利用できます。

構文

ProxyPassReverse [リクエストパス] [バックエンドURL]


ProxyPassと合わせて通常このように設定されます。

ProxyPass /app/ http://localhost:8080/myapp/
ProxyPassReverse /app/ http://localhost:8080/myapp/

この設定により、例えばバックエンドサーバーが Location: http://localhost:8080/myapp/success というヘッダーを返した場合、Apacheはそれを Location: http://www.example.com/app/success に書き換えてクライアントに送信します。

ProxyPassReverse が特に役立つケース

  • セッション管理
    セッション管理において、バックエンドサーバーが生成するセッションIDや関連するURLが正しくクライアントに伝わるようにするために重要です。
  • クッキーのドメイン書き換え
    一部のアプリケーションでは、クッキーのドメインがバックエンドサーバーの内部ドメインに設定されていることがあります。ProxyPassReverseは、そのようなクッキーもリバースプロキシのドメインに書き換えることで、クライアントがクッキーを正しく利用できるようにします(ただし、これはProxyPassReverseCookieDomainProxyPassReverseCookiePathといった別のディレクティブで詳細な制御が必要です)。
  • リダイレクトの処理
    バックエンドサーバーが認証後に特定のページにリダイレクトする場合など、Location:ヘッダーを書き換えることで、クライアントが正しくアクセスできるようにします。


ProxyPassReverseはリバースプロキシの重要なディレクティブですが、設定ミスやバックエンドサーバーの挙動によって問題が発生することがよくあります。ここでは、一般的なエラーとそのトラブルシューティング方法を説明します。

リダイレクトが正しく行われない (クライアントがバックエンドの内部URLにリダイレクトされる)

症状
ブラウザでリバースプロキシのURLにアクセスすると、バックエンドサーバーからのリダイレクトが発生した際に、ブラウザのアドレスバーにリバースプロキシのURLではなく、バックエンドサーバーの内部URL(例: http://localhost:8080/myapp/login)が表示されてしまう。これにより、クライアントはバックエンドに直接アクセスしようとしてエラーになったり、ページが見つからなかったりします。

原因
ProxyPassReverseディレクティブが正しく設定されていないか、完全に欠落している可能性があります。ProxyPassReverseは、バックエンドサーバーが返すLocation:ヘッダーを書き換える役割を担っています。

トラブルシューティング

  • Apacheのリロード/再起動
    設定ファイルを変更した後は、Apacheをリロードまたは再起動して変更を適用しているか確認してください。

  • HTTPヘッダーの確認
    開発者ツール(ChromeのDevToolsなど)でネットワークタブを確認し、リダイレクトが発生した際のHTTP応答ヘッダーのLocation:を確認します。もし内部URLがそのままになっている場合は、ProxyPassReverseが機能していません。

  • ProxyPassReverseディレクティブの確認
    ProxyPassディレクティブに対応するProxyPassReverseディレクティブが設定されているか確認してください。両者のパスは一致している必要があります。

    誤った例

    ProxyPass /app/ http://localhost:8080/myapp/
    # ProxyPassReverseが設定されていない
    
    ProxyPass /app/ http://localhost:8080/myapp/
    ProxyPassReverse /app/ http://localhost:8080/myapp/
    

HTMLコンテンツ内のリンクが正しく書き換わらない

症状
ページは表示されるが、そのページ内の画像、CSS、JavaScriptなどのリンク(<a>, <img>, <link>, <script>タグなど)がバックエンドサーバーの内部URLを参照しており、正しく表示されない、または機能しない。

原因
ProxyPassReverseはHTTPヘッダー(主にLocation:ヘッダー)を書き換える機能しか持っていません。HTMLコンテンツのボディ部分に埋め込まれたURLは書き換えません。バックエンドアプリケーションが絶対パス(例: /css/style.css)ではなく、内部の完全なURL(例: http://backend-server:8080/css/style.css)でリソースを生成している場合にこの問題が発生します。

トラブルシューティング

  • mod_rewriteの利用 (一部のケース)
    複雑なケースでは、mod_rewriteを使用して、プロキシが返すコンテンツの一部を書き換えることも考えられますが、これはHTMLのパースを伴わないため、確実性に欠け、パフォーマンスに影響を与える可能性があります。

  • mod_proxy_htmlの利用
    Apacheのmod_proxy_htmlモジュールを使用すると、HTMLコンテンツ内のURLを動的に書き換えることができます。これは強力ですが、設定が複雑になる場合があります。

    設定例

    # mod_proxy_html を有効にする
    LoadModule proxy_html_module modules/mod_proxy_html.so
    LoadModule xml2enc_module modules/mod_xml2enc.so # 必要に応じて
    
    <Location /app/>
        ProxyPass http://localhost:8080/myapp/
        ProxyPassReverse http://localhost:8080/myapp/
        ProxyHTMLURLMap http://localhost:8080/myapp/ /app/
        ProxyHTMLURLMap /myapp/ /app/
        SetOutputFilter INFLATE;DEFLATE;SUBSTITUTE;proxy-html
        # AddOutputFilterByType text/html proxy-html # 古いApacheの場合
        RequestHeader unset Accept-Encoding # 圧縮されていると書き換えが難しい場合がある
    </Location>
    
    • ProxyHTMLURLMap [元のURL] [新しいURL]:HTML内のURLを書き換えるルールを定義します。
    • SetOutputFilter proxy-htmlproxy-htmlフィルタを適用してHTMLを書き換えるようにします。
    • RequestHeader unset Accept-Encoding:バックエンドサーバーが圧縮されたコンテンツを返す場合、mod_proxy_htmlが処理できないことがあります。このヘッダーを削除することで、非圧縮のコンテンツを要求し、書き換えを可能にします。
  • バックエンドアプリケーションの設定変更
    最も良い解決策は、バックエンドアプリケーションが生成するURLを相対パスにするか、またはリバースプロキシの公開URLを考慮した絶対パス(例: /app/css/style.css)で生成するように変更することです。

SSL/TLS関連の問題 (HTTPSリバースプロキシ)

症状
HTTPSでリバースプロキシを設定しているが、バックエンドサーバーへの接続でエラーが発生したり、クライアントに証明書のエラーが表示されたりする。

原因

  • SNI (Server Name Indication) の問題。
  • バックエンドサーバーのSSL証明書が、Apacheの信頼ストアにないか、証明書検証に失敗している。
  • Apacheがバックエンドサーバーへの接続にHTTPSを使用するように設定されていない。

トラブルシューティング

  • ProxyPreserveHost On
    バックエンドサーバーがHostヘッダーに依存して動作する場合(例: 仮想ホストの設定)、ProxyPreserveHost Onを設定することで、クライアントから受け取ったHostヘッダーをそのままバックエンドに転送できます。

    ProxyPreserveHost On
    
  • バックエンド証明書の検証
    本番環境では、SSLProxyVerify requireSSLProxyCACertificateFileを使用して、バックエンドサーバーの証明書を検証することを強く推奨します。これにより、中間者攻撃などを防ぎます。

  • SSLProxyEngine Onの設定
    ApacheがバックエンドサーバーへのSSL接続を確立できるようにするために必要です。

    <VirtualHost *:443>
        ServerName www.example.com
        SSLEngine On
        SSLCertificateFile /path/to/your_certificate.crt
        SSLCertificateKeyFile /path/to/your_private.key
    
        ProxyPass /app/ https://localhost:8443/myapp/
        ProxyPassReverse /app/ https://localhost:8443/myapp/
    
        SSLProxyEngine On # これが重要
        # 必要に応じてバックエンド証明書の検証を設定
        # SSLProxyVerify require
        # SSLProxyCACertificateFile /path/to/backend_ca_certificate.crt
    </VirtualHost>
    
  • mod_sslとmod_proxy_httpの有効化
    HTTPSリバースプロキシにはこれらのモジュールが必須です。

    LoadModule ssl_module modules/mod_ssl.so
    LoadModule proxy_http_module modules/mod_proxy_http.so
    
  • ProxyPassとProxyPassReverseでhttps://を指定
    バックエンドがHTTPSである場合、ProxyPassProxyPassReverseのバックエンドURLもhttps://にする必要があります。

    ProxyPass /app/ https://localhost:8443/myapp/
    ProxyPassReverse /app/ https://localhost:8443/myapp/
    

ProxyPassとProxyPassReverseの順序、またはLocationブロックとの関係

症状
設定が正しいはずなのにリバースプロキシが動作しない、または意図しないパスにリクエストが転送される。

原因
Apacheの設定ファイル(httpd.conf.confファイル)におけるディレクティブの順序、特にProxyPassProxyPassReverseの順序が影響する場合があります。Apacheは最も具体的なパスに一致するルールを優先します。

トラブルシューティング

  • <Location>または<VirtualHost>ブロック内での設定
    ProxyPassProxyPassReverseは、通常<VirtualHost>または<Location>ブロック内で使用されます。これにより、特定のドメインやパスに対してプロキシ設定を適用できます。

    <VirtualHost *:80>
        ServerName example.com
        ProxyRequests Off # フォワードプロキシを無効にする(リバースプロキシでは通常Off<Proxy *>
            Require all granted # アクセス制御
        </Proxy>
    
        <Location /app/>
            ProxyPass http://localhost:8080/myapp/
            ProxyPassReverse http://localhost:8080/myapp/
        </Location>
    </VirtualHost>
    
  • より具体的なパスを先に記述
    例えば、/app/api//app/の両方をプロキシする場合、/app/api/のようなより具体的なパスを先に記述します。

    ProxyPass /app/api/ http://localhost:8080/myapp/api/
    ProxyPassReverse /app/api/ http://localhost:8080/myapp/api/
    ProxyPass /app/ http://localhost:8080/myapp/
    ProxyPassReverse /app/ http://localhost:8080/myapp/
    

mod_proxy関連モジュールの有効化忘れ

症状
Apacheのエラーログに「No protocol handler was valid for the URL」のようなエラーが表示される、またはプロキシが全く機能しない。

原因
mod_proxyのサブモジュールが有効になっていない可能性があります。ProxyPassProxyPassReverseを使用するには、mod_proxyに加えて、使用するプロトコルに応じたサブモジュールが必要です。

トラブルシューティング

  • Apacheの再起動
    モジュールを有効にした後は、必ずApacheを再起動してください。

  • 必要なモジュールの有効化

    • HTTPプロキシの場合: mod_proxy.somod_proxy_http.so
    • AJPプロキシの場合: mod_proxy.somod_proxy_ajp.so
    • WebSocketsプロキシの場合: mod_proxy.somod_proxy_wstunnel.so
    • ロードバランシングの場合: mod_proxy_balancer.somod_lbmethod_byrequests.so など

    httpd.confまたは対応する設定ファイルで、以下の行のコメントアウトを解除して有効にします。

    LoadModule proxy_module modules/mod_proxy.so
    LoadModule proxy_http_module modules/mod_proxy_http.so
    

    (Linuxディストリビューションによってはa2enmodコマンドで有効化します。例: sudo a2enmod proxy proxy_http

タイムアウト関連のエラー (502 Bad Gatewayなど)

症状
クライアントに502 Bad Gateway504 Gateway Timeoutエラーが表示される。

原因
バックエンドサーバーからの応答が遅い、またはバックエンドサーバーがダウンしている可能性があります。Apacheのプロキシタイムアウト設定が短すぎる場合もこのエラーが発生します。

トラブルシューティング

  • ProxyPassのtimeoutパラメータ
    ProxyPassディレクティブに直接timeoutパラメータを追加することもできます。

    ProxyPass /app/ http://localhost:8080/myapp/ timeout=300
    
  • Apacheのタイムアウト設定の調整
    ProxyTimeoutディレクティブを使用して、プロキシがバックエンドサーバーからの応答を待つ時間を長くすることができます。

    ProxyTimeout 300 # 300秒(5分)に設定
    

    これはVirtualHostDirectoryLocation、またはサーバー全体に適用できます。

  • バックエンドサーバーの稼働状況確認
    バックエンドサーバーが正常に動作しているか確認してください。直接アクセスして動作するか、ログにエラーがないかを確認します。

クッキー関連の問題

症状
リバースプロキシ経由でアクセスすると、セッションが維持されない、またはログインがうまくいかない。

原因
バックエンドサーバーが発行するクッキーのドメインやパスが、クライアントがアクセスしているリバースプロキシのドメインやパスと一致しないため、クッキーが正しく保存・送信されないことがあります。

トラブルシューティング

  • ProxyPassReverseCookiePath
    バックエンドが設定するクッキーのパスを、リバースプロキシの公開パスに書き換えます。

    ProxyPassReverseCookiePath /backend/ /public/
    

    これらのディレクティブは、特に複雑なアプリケーションでセッション管理がクッキーに依存している場合に重要です。

  • ProxyPassReverseCookieDomain
    バックエンドが設定するクッキーのドメインを、リバースプロキシの公開ドメインに書き換えます。

    ProxyPassReverseCookieDomain backend.example.com public.example.com
    

一般的なトラブルシューティングのヒント

  • バックエンドサーバーへの直接アクセス
    問題がApacheの設定にあるのか、バックエンドサーバーにあるのかを切り分けるために、可能であればバックエンドサーバーに直接アクセスして、正常に動作するかどうかを確認します。

  • 設定ファイルの構文チェック
    設定ファイルを変更した後、Apacheを再起動する前に構文エラーがないかチェックします。

    apachectl configtest
    # または
    httpd -t
    
  • LogLevel debug
    一時的にApacheのLogLeveldebugに設定すると、より詳細なデバッグ情報をログに出力させることができます。ただし、本番環境ではディスクスペースを大量に消費するため、問題解決後に元に戻すことを忘れないでください。

    LogLevel debug
    
  • Apacheのエラーログとアクセスログの確認
    最も重要なステップです。エラーログ(通常/var/log/apache2/error.log/var/log/httpd/error_log)には、問題の根本原因に関する情報が詳細に記録されています。アクセスログもリクエストがどのように処理されているかを確認するのに役立ちます。

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


mod_proxy を使ってリバースプロキシを構築する際、ProxyPassReverse はバックエンドサーバーからのリダイレクトを適切に処理するために非常に重要なディレクティブです。ここでは、いくつかの一般的なシナリオにおける設定例を提示します。

前提条件

これらの設定例を使用する前に、以下の Apache モジュールが有効になっていることを確認してください。通常、これらは /etc/httpd/conf/httpd.conf/etc/apache2/mods-available/ ディレクトリ内で設定されます。

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
# 必要に応じて、他のプロキシ関連モジュールも有効にする
# LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
# LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
# LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
# LoadModule proxy_html_module modules/mod_proxy_html.so
# LoadModule xml2enc_module modules/mod_xml2enc.so

モジュールを有効にした後、Apache を再起動またはリロードすることを忘れないでください。 例: sudo systemctl restart apache2 または sudo apachectl restart

基本的なリバースプロキシ設定 (HTTP)

最も一般的な設定です。クライアントからの /app/ へのリクエストを、バックエンドサーバーの http://localhost:8080/myapp/ に転送し、バックエンドからの応答ヘッダーを書き換えます。

# httpd.conf または VirtualHost の設定ファイルに記述

<VirtualHost *:80>
    ServerName www.example.com
    # プロキシとして機能させるため、フォワードプロキシの機能を無効にする(推奨)
    ProxyRequests Off

    # クライアントからのHostヘッダーをバックエンドにそのまま転送する
    # バックエンドアプリケーションがHostヘッダーに基づいて動作する場合に重要
    ProxyPreserveHost On

    # /app/ へのリクエストを http://localhost:8080/myapp/ へプロキシ
    ProxyPass /app/ http://localhost:8080/myapp/

    # バックエンドからのLocation: ヘッダーなどを書き換える
    # 例: Location: http://localhost:8080/myapp/login を Location: http://www.example.com/app/login に書き換える
    ProxyPassReverse /app/ http://localhost:8080/myapp/

    # プロキシされた接続に関する基本的なアクセス制御
    <Proxy *>
        Require all granted
    </Proxy>

    ErrorLog ${APACHE_LOG_DIR}/app_error.log
    CustomLog ${APACHE_LOG_DIR}/app_access.log combined
</VirtualHost>

HTTPSリバースプロキシ設定 (SSL/TLS終端)

ApacheでSSL/TLSを終端し、バックエンドサーバーにはHTTPで接続するシナリオです。

# mod_ssl モジュールが有効になっていることを確認
# LoadModule ssl_module modules/mod_ssl.so

<VirtualHost *:443>
    ServerName secure.example.com
    ProxyRequests Off
    ProxyPreserveHost On

    # SSL/TLS設定
    SSLEngine On
    SSLCertificateFile /etc/ssl/certs/your_certificate.crt
    SSLCertificateKeyFile /etc/ssl/private/your_private.key
    # SSLCertificateChainFile /etc/ssl/certs/your_chain.crt # 必要に応じて

    # /secureapp/ へのHTTPSリクエストを http://localhost:8080/myapp/ へプロキシ
    ProxyPass /secureapp/ http://localhost:8080/myapp/
    ProxyPassReverse /secureapp/ http://localhost:8080/myapp/

    # クッキーのドメイン書き換えが必要な場合 (バックエンドがクッキーのドメインを設定する場合)
    # 例: バックエンドが .localhost にクッキーを設定した場合、.secure.example.com に書き換える
    # ProxyPassReverseCookieDomain "localhost" "secure.example.com"
    # ProxyPassReverseCookiePath "/myapp/" "/secureapp/" # クッキーのパス書き換えが必要な場合

    <Proxy *>
        Require all granted
    </Proxy>

    ErrorLog ${APACHE_LOG_DIR}/secureapp_error.log
    CustomLog ${APACHE_LOG_DIR}/secureapp_access.log combined
</VirtualHost>

バックエンドもHTTPSの場合

Apacheとバックエンドサーバー間の通信もHTTPSで行うシナリオです。

# mod_ssl と mod_proxy_http モジュールが有効になっていることを確認

<VirtualHost *:443>
    ServerName secure.example.com
    ProxyRequests Off
    ProxyPreserveHost On

    # SSL/TLS設定 (クライアントとの通信用)
    SSLEngine On
    SSLCertificateFile /etc/ssl/certs/your_certificate.crt
    SSLCertificateKeyFile /etc/ssl/private/your_private.key

    # ApacheがバックエンドへのSSL接続を開始できるようにする
    SSLProxyEngine On

    # バックエンドサーバーの証明書を検証する場合 (本番環境で推奨)
    # SSLProxyVerify require
    # SSLProxyCACertificateFile /etc/ssl/certs/backend_ca.crt

    # /secureapp/ へのHTTPSリクエストを https://localhost:8443/myapp/ へプロキシ
    ProxyPass /secureapp/ https://localhost:8443/myapp/
    ProxyPassReverse /secureapp/ https://localhost:8443/myapp/

    <Proxy *>
        Require all granted
    </Proxy>

    ErrorLog ${APACHE_LOG_DIR}/secureapp_error.log
    CustomLog ${APACHE_LOG_DIR}/secureapp_access.log combined
</VirtualHost>

HTMLコンテンツ内のURLを書き換える (mod_proxy_html の利用)

ProxyPassReverseはHTTPヘッダーのみを書き換えますが、HTMLコンテンツ内のURLを書き換えたい場合はmod_proxy_htmlを使用します。

# mod_proxy_html と mod_xml2enc モジュールが有効になっていることを確認
# LoadModule proxy_html_module modules/mod_proxy_html.so
# LoadModule xml2enc_module modules/mod_xml2enc.so

<VirtualHost *:80>
    ServerName www.example.com
    ProxyRequests Off
    ProxyPreserveHost On

    <Location /app/>
        ProxyPass http://localhost:8080/myapp/
        ProxyPassReverse http://localhost:8080/myapp/

        # HTMLコンテンツ内のURLを書き換える
        # 例: <a href="http://localhost:8080/myapp/page1.html"> を <a href="/app/page1.html"> に
        # 例: <img src="/myapp/images/logo.png"> を <img src="/app/images/logo.png"> に
        ProxyHTMLURLMap http://localhost:8080/myapp/ /app/
        ProxyHTMLURLMap /myapp/ /app/

        # 出力フィルタとして proxy-html を適用
        # 圧縮されているコンテンツの書き換えを可能にするために Accept-Encoding を削除
        SetOutputFilter INFLATE;DEFLATE;proxy-html
        RequestHeader unset Accept-Encoding

        Require all granted
    </Location>

    ErrorLog ${APACHE_LOG_DIR}/app_html_error.log
    CustomLog ${APACHE_LOG_DIR}/app_html_access.log combined
</VirtualHost>

注意
mod_proxy_html はHTMLをパースして書き換えるため、処理負荷が高くなる可能性があります。また、JavaScriptで動的に生成されるURLや、CSSファイル内のURLは書き換えられない場合があります。可能な限り、バックエンドアプリケーション側で正しい相対パスまたはプロキシ後のURLを生成するように設定するのが理想的です。

ロードバランシング設定 (複数のバックエンドサーバー)

mod_proxy_balancer を使用して、複数のバックエンドサーバーにリクエストを分散し、各バックエンドからの応答ヘッダーも適切に処理します。

# mod_proxy_balancer, mod_lbmethod_byrequests (または他のロードバランシングメソッド) が有効になっていることを確認

<VirtualHost *:80>
    ServerName lb.example.com
    ProxyRequests Off
    ProxyPreserveHost On

    # バランサーの定義
    <Proxy balancer://mycluster>
        BalancerMember http://backend1.local:8080/myapp/ loadfactor=1
        BalancerMember http://backend2.local:8080/myapp/ loadfactor=1
        # もし特定のURLでセッション維持が必要なら
        # ProxySet stickysession=JSESSIONID|jsessionid nofailover=On
        ProxySet lbmethod=byrequests # リクエスト数に基づくロードバランシング
    </Proxy>

    # /clusterapp/ へのリクエストをバランサーに転送
    ProxyPass /clusterapp/ balancer://mycluster/

    # バックエンドからのLocation: ヘッダーなどを書き換える
    # ここでのバックエンドURLは、BalancerMember のパスではなく、ProxyPass のターゲットとなるバランサー名
    # バランサーの内部URLは ProxyPassReverse のターゲットになりません
    # ProxyPassReverse は、クライアントから見えるURLと、元のバックエンドURLをペアで指定します
    ProxyPassReverse /clusterapp/ http://backend1.local:8080/myapp/
    ProxyPassReverse /clusterapp/ http://backend2.local:8080/myapp/
    # バランサー経由の場合でも、個々のバックエンドURLに対してProxyPassReverseを設定する必要があります。
    # バックエンドがリダイレクトを返した場合、Location: ヘッダーにはそのバックエンド自身のURLが含まれるためです。

    <Proxy *>
        Require all granted
    </Proxy>

    ErrorLog ${APACHE_LOG_DIR}/lb_error.log
    CustomLog ${APACHE_LOG_DIR}/lb_access.log combined
</VirtualHost>

注意
ロードバランシング環境におけるProxyPassReverseは、通常、すべてのBalancerMemberのバックエンドURLに対して個別に設定する必要があります。バックエンドがリダイレクトを返した場合、そのLocation:ヘッダーは特定のバックエンドの内部URLを参照するためです。



ProxyPassReverseは非常に便利ですが、以下のような状況では他の方法を検討する必要があるかもしれません。

  • アプリケーション自体でのURL生成の制御
  • Apache以外のリバースプロキシソリューションの利用
  • より複雑なURL書き換えロジック
  • HTTPヘッダー以外のコンテンツ(HTMLボディ、JavaScriptなど)内のURL書き換え

Apache mod_proxy_html を利用したコンテンツ書き換え

ProxyPassReverseがヘッダーのみを対象とするのに対し、mod_proxy_htmlはHTMLコンテンツ内のURLを書き換えることに特化したモジュールです。これは、バックエンドアプリケーションが絶対パス(例: http://internal-backend:8080/image.jpg)でリソースのURLを生成してしまう場合に非常に有効です。

特徴

  • href, src, action などのHTML属性だけでなく、@importurl() などのCSS内のURLも対象にできる。
  • HTMLドキュメントをパースし、指定された正規表現に基づいてURLを書き換える。

設定例

LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule xml2enc_module modules/mod_xml2enc.so # 必要に応じて

<Location /app/>
    ProxyPass http://localhost:8080/myapp/
    ProxyPassReverse http://localhost:8080/myapp/ # ヘッダー書き換えは併用
    
    # HTML内のURLを書き換えるルール
    # 旧URL: http://localhost:8080/myapp/ を 新URL: /app/ に書き換え
    ProxyHTMLURLMap http://localhost:8080/myapp/ /app/
    # 旧URL: /myapp/ を 新URL: /app/ に書き換え (絶対パスでない場合も対応)
    ProxyHTMLURLMap /myapp/ /app/

    # 出力フィルタとして mod_proxy_html を適用
    # バックエンドが圧縮応答を返す場合、RequestHeader unset Accept-Encoding で非圧縮を要求すると確実
    SetOutputFilter INFLATE;DEFLATE;proxy-html
    RequestHeader unset Accept-Encoding
</Location>

注意点

  • 完全ではない: JavaScriptで動的に生成されるURLや、HTML以外のファイル(JSON APIのレスポンスなど)内のURLは書き換えられません。
  • パフォーマンスへの影響: HTMLをパースして書き換えるため、処理負荷が高くなる可能性があります。

Apache mod_substitute を利用した汎用的な文字列置換

mod_substitute は、HTTP応答のボディに対して汎用的な文字列置換(正規表現も利用可能)を行うためのモジュールです。mod_proxy_html がHTMLの構造を理解するのに対し、mod_substitute はバイトストリームとして単純な文字列置換を行います。

特徴

  • mod_proxy_htmlよりも単純なため、オーバーヘッドが少ない可能性がある。
  • HTMLだけでなく、JSON、XML、プレーンテキストなど、様々なコンテンツに適用可能。
  • コンテンツタイプに関わらず、指定されたパターンで文字列を置換できる。

設定例

LoadModule substitute_module modules/mod_substitute.so

<Location /app/>
    ProxyPass http://localhost:8080/myapp/
    ProxyPassReverse http://localhost:8080/myapp/ # ヘッダー書き換えは併用

    # 出力フィルタとして mod_substitute を適用
    AddOutputFilterByType SUBSTITUTE text/html text/javascript application/json
    
    # HTTP応答ボディ内の文字列を置換
    # 例: "http://localhost:8080/myapp/" を "/app/" に置換 (大文字小文字を区別しない)
    Substitute "s|http://localhost:8080/myapp/|/app/|i"
    # 例: "/myapp/" を "/app/" に置換
    Substitute "s|/myapp/|/app/|i"

    # 必要に応じて、コンテンツが圧縮されている場合は非圧縮を要求
    RequestHeader unset Accept-Encoding
</Location>

注意点

  • バイナリコンテンツには不向き: テキストベースのコンテンツに限定されます。
  • 誤変換のリスク: HTMLタグの属性値だけでなく、本文中の文字列なども置換対象となるため、意図しない書き換えが発生する可能性があります。

mod_rewrite を利用した複雑なリダイレクト処理 (限定的)

mod_rewrite は非常に強力なURL書き換えモジュールですが、主にリクエストURLの書き換えや、クライアントへのリダイレクト(3xxステータスコード)生成に使用されます。 応答ヘッダーのLocation:を書き換える用途にはProxyPassReverseが最適であり、mod_rewriteで代替するのは複雑で非効率的です。

ただし、バックエンドが返すLocationヘッダーのURLがProxyPassReverseのシンプルな変換で対応できない場合(例えば、バックエンドが異なるサブドメインにリダイレクトする場合など)、mod_rewriteHeaderディレクティブと正規表現を組み合わせて応答ヘッダーを書き換えることが理論的には可能です。

例 (複雑なケースの概念)

LoadModule rewrite_module modules/mod_rewrite.so
LoadModule headers_module modules/mod_headers.so

<VirtualHost *:80>
    ServerName www.example.com
    ProxyRequests Off
    ProxyPreserveHost On

    RewriteEngine On
    
    ProxyPass /app/ http://localhost:8080/myapp/
    # ProxyPassReverse はここでも使用するのが推奨される
    ProxyPassReverse /app/ http://localhost:8080/myapp/

    # もしバックエンドが http://backend.internal.com/login のように
    # 全く異なるドメインにリダイレクトしようとする場合、
    # ProxyPassReverse だけでは対応できないことがある。
    # その場合、HeadersOut で Location ヘッダーを直接操作する。
    # これは非常に限定的なケースであり、通常は ProxyPassReverse で十分
    
    # 応答ヘッダーのLocationを書き換える例 (ProxyPassReverseが効かない特殊な場合)
    # バックエンドが Location: http://internal.backend.com/login を返す場合、
    # これを http://www.example.com/app/login に書き換える
    Header edit Location "^http://internal\.backend\.com/(.*)$" "http://www.example.com/app/$1"
</VirtualHost>

注意点

  • 通常のリダイレクト処理にはProxyPassReverseが常に優先されるべきです。
  • mod_rewriteHeaderディレクティブは、あくまでHTTPヘッダーの文字列置換を行うものです。ProxyPassReverseのように、プロキシパスとの関連性を自動で考慮してくれるわけではないため、より慎重な正規表現の記述が必要です。

アプリケーションレベルでのURL生成の制御

最も推奨される代替方法です。バックエンドアプリケーション自身が、自分がリバースプロキシの裏側で動作していることを認識し、すべてのURL(リダイレクト、HTMLコンテンツ内のリンク、APIレスポンス内のURLなど)を、クライアントがアクセスする公開URL(リバースプロキシのURL)に基づいて生成するように設計・設定します。

特徴

  • クッキーのドメインやパスなど、ProxyPassReversemod_proxy_htmlでは対応しきれない複雑なURL要件にも対応しやすい。
  • Apacheなどのプロキシサーバーの負荷を軽減できる。
  • 最も堅牢でエラーが少ない方法。

実装方法の例

  • HTTPヘッダーの利用
    リバースプロキシが X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-For などのヘッダーをバックエンドに転送し、バックエンドアプリケーションがこれらのヘッダーを解析して自身のURLを構築する。
  • Webフレームワーク/CMSの設定
    多くのモダンなWebフレームワーク(Ruby on Rails, Django, Laravel, Spring Bootなど)やCMS(WordPress, Drupalなど)には、プロキシ環境で動作するための設定オプション(例: ROOT_URL, PUBLIC_URL, TRUSTED_PROXIESなど)が用意されています。

例 (Apache側でX-Forwardedヘッダーを転送)

<VirtualHost *:80>
    ServerName www.example.com
    ProxyRequests Off
    ProxyPreserveHost On

    # クライアントのプロトコル、ホスト、IPアドレスをバックエンドに伝える
    RequestHeader set X-Forwarded-Proto "http"
    RequestHeader set X-Forwarded-Host "www.example.com"
    # RequestHeader set X-Forwarded-Host $host # 動的にホスト名を渡す場合
    RequestHeader set X-Forwarded-For %{REMOTE_ADDR}

    ProxyPass /app/ http://localhost:8080/myapp/
    ProxyPassReverse /app/ http://localhost:8080/myapp/
    
    <Proxy *>
        Require all granted
    </Proxy>
</VirtualHost>

バックエンドアプリケーションは、例えば X-Forwarded-Protohttps で、X-Forwarded-Hostwww.example.com であれば、それらの情報を使ってURLを生成します。

別のリバースプロキシソフトウェアの利用

Apache HTTP Server以外にも、リバースプロキシ機能に特化したソフトウェアは多数あり、それらはLocationヘッダーの書き換えやコンテンツ書き換えに関して異なるアプローチを提供しています。

  • HAProxy
    高度なロードバランシングとプロキシ機能を提供します。HTTPヘッダーやコンテンツの書き換えを細かく制御できます。

    frontend http_front
        bind *:80
        mode http
        default_backend webservers
    
    backend webservers
        mode http
        balance roundrobin
        server backend1 localhost:8080 check
    
        # Locationヘッダーを書き換える
        http-response replace-header Location ^http://localhost:8080/(.*) http://my.public.com/\1
        # コンテンツを書き換える(HAProxyは直接的なコンテンツ書き換え機能が限定的で、通常は別途Webサーバーやフィルタが必要)
        # ただし、http-response set-header Content-Security-Policy などでヘッダーを追加・変更できる
    
  • Nginx
    軽量で高性能なリバースプロキシとして広く利用されています。proxy_redirectディレクティブでLocationヘッダーを書き換え、sub_filterディレクティブで応答ボディ内の文字列を置換できます。

    location /app/ {
        proxy_pass http://localhost:8080/myapp/;
        # Locationヘッダーを書き換え
        proxy_redirect http://localhost:8080/myapp/ /app/;
        # 応答ボディを書き換え
        sub_filter 'http://localhost:8080/myapp/' '/app/';
        sub_filter_once off; # すべての出現を置換
    }
    

ProxyPassReverseはHTTP応答ヘッダーのURL書き換えに特化した強力なツールですが、HTMLコンテンツ内のURL書き換えにはmod_proxy_htmlmod_substitute、またはより根本的な解決策としてアプリケーション側でのURL生成の制御が推奨されます。