Apache ProxyPreserveHostエラー解決!Hostヘッダーが渡らない原因と対策

2025-05-27

もう少し詳しく説明します。

通常、Apacheがリバースプロキシとして機能する場合、バックエンドサーバーへのリクエストのHostヘッダーは、Apache自身の設定(例えば、ServerNameVirtualHostの設定)に基づいて書き換えられることがあります。

しかし、「ProxyPreserveHost On」を設定すると、このデフォルトの挙動が変わり、クライアントが最初に送信したHostヘッダー(例えば、「www.example.com」など)が、Apacheを経由してバックエンドサーバーにそのまま伝えられます。

この設定が重要なのは、以下のようなケースです。

  • バックエンドのアプリケーションがHostヘッダーを参照している場合
    一部のアプリケーションは、リダイレクトURLの生成や、ログ記録などの目的でHostヘッダーの値を利用します。この場合、オリジナルのHostヘッダーを保持しないと、アプリケーションの動作に問題が生じる可能性があります。
  • 複数のドメインを一つのバックエンドサーバーで処理している場合
    バックエンドのアプリケーションが、Hostヘッダーの値に基づいて異なるコンテンツや振る舞いを提供している場合、オリジナルのHostヘッダーを保持することで、アプリケーションはどのドメインへのリクエストかを正しく認識できます。


クライアントが「http://www.example.com/somepage」にリクエストを送ったとします。

  • ProxyPreserveHost On
    Apacheはバックエンドサーバーにリクエストを転送する際に、Hostヘッダーをクライアントが送ってきた「www.example.com」のまま送信します。
  • ProxyPreserveHost Off (デフォルト)
    Apacheはバックエンドサーバーにリクエストを転送する際に、HostヘッダーをApache自身の設定に基づいた値(例えば、バックエンドサーバーのIPアドレスや設定された内部ホスト名)に書き換える可能性があります。


一般的なエラーとトラブルシューティング

    • エラー
      設定ファイル(通常は httpd.conf や VirtualHost の設定ファイル)で、ProxyPreserveHost のスペルミスや記述ミスがある。または、意図しない場所(例えば <Directory> セクション内など)に記述されている。
    • トラブルシューティング
      設定ファイルを再度確認し、ProxyPreserveHost On が正しいスペルで、<VirtualHost> セクションまたはグローバルなサーバー設定の適切な場所に記述されているかを確認してください。Apache の設定構文は厳密なので、わずかなミスでもエラーの原因になります。設定変更後は、Apache の設定ファイルを再読み込みまたは再起動して、変更を反映させる必要があります (sudo systemctl reload httpdsudo service apache2 restart など、システムによってコマンドは異なります)。
  1. 複数のプロキシを経由している場合

    • エラー
      Apache の手前にさらに別のロードバランサーやプロキシが存在する場合、最初のプロキシが Host ヘッダーを書き換えてしまっている可能性があります。この場合、Apache に届く時点でオリジナルの Host ヘッダーが失われています。
    • トラブルシューティング
      ネットワーク構成全体を確認し、手前のプロキシが Host ヘッダーをどのように処理しているかを確認してください。もし手前のプロキシで Host ヘッダーが書き換えられている場合は、そのプロキシの設定を変更するか、別のヘッダー(例えば X-Forwarded-Host など)でオリジナルの Host 情報をバックエンドに渡すように構成し、バックエンドアプリケーション側でそのヘッダーを参照するように変更する必要があります。
  2. バックエンドサーバーの設定ミス

    • エラー
      バックエンドサーバー側の設定が、オリジナルの Host ヘッダーを受け取るように構成されていない場合があります。例えば、特定の Host 名でのアクセスしか許可していない、または Host ヘッダーを無視する設定になっているなど。
    • トラブルシューティング
      バックエンドサーバーの設定(Web サーバーやアプリケーションサーバーの設定)を確認し、ProxyPreserveHost On で渡される Host ヘッダーを正しく処理するように構成されているかを確認してください。ログなどを確認することで、バックエンドサーバーがどのような Host ヘッダーを受け取っているかを確認できます。
  3. VirtualHost の設定の優先順位

    • エラー
      複数の VirtualHost が設定されており、リクエストが意図しない VirtualHost で処理されている場合、その VirtualHost で ProxyPreserveHostOff になっている可能性があります。
    • トラブルシューティング
      Apache の VirtualHost の設定を確認し、リクエストがどの VirtualHost で処理されているかを確認してください。apachectl -S コマンドなどで、VirtualHost の設定と優先順位を確認できます。目的の VirtualHost で ProxyPreserveHost On が設定されているかを確認してください。
  4. モジュールのロードの問題

    • エラー
      mod_proxy モジュール自体がロードされていない場合、ProxyPreserveHost ディレクティブは認識されません。
    • トラブルシューティング
      Apache の設定ファイルで mod_proxymod_proxy_http モジュールがロードされていることを確認してください (LoadModule proxy_module modules/mod_proxy.soLoadModule proxy_http_module modules/mod_proxy_http.so のような記述があるか)。ロードされていない場合は、これらの行のコメントアウトを解除し、Apache を再起動してください。
  5. ネットワークの問題

    • エラー
      ファイアウォールやネットワークの設定によって、Host ヘッダーを含むリクエストがバックエンドサーバーに正しく届いていない可能性があります(可能性は低いですが)。
    • トラブルシューティング
      ネットワークの接続状況やファイアウォールの設定を確認してください。Apache サーバーからバックエンドサーバーへの通信が正常に行われているかを確認するために、pingtelnet などのコマンドを使用できます。
  6. バックエンドアプリケーションのエラー

    • エラー
      ProxyPreserveHost On の設定は正しくても、バックエンドアプリケーション自体が受け取った Host ヘッダーを正しく処理できていない場合があります。
    • トラブルシューティング
      バックエンドアプリケーションのログや動作を確認し、受け取った Host ヘッダーがアプリケーションの期待する値になっているか、またその値が正しく処理されているかを確認してください。

トラブルシューティングのヒント

  • 簡単なテストを行う
    curl コマンドなどで、Host ヘッダーを明示的に指定してリクエストを送信し、バックエンドサーバーがどのように応答するかを確認するのも有効な手段です。例えば、curl -H "Host: www.example.com" http://your_apache_server/somepage のように実行して、バックエンドサーバーが www.example.com として認識しているかを確認できます。
  • バックエンドサーバーのログを確認する
    バックエンドサーバーのログ(Web サーバーやアプリケーションサーバーのログ)を確認し、どのような Host ヘッダーを受け取っているか、エラーが発生していないかなどを確認します。
  • アクセスログを確認する
    Apache のアクセスログ (CustomLog ディレクティブで指定されたファイル) を確認し、リクエストがどのように処理されているか、どの VirtualHost が処理しているかなどを確認できます。
  • Apache のエラーログを確認する
    Apache のエラーログ (ErrorLog ディレクティブで指定されたファイル) に、設定エラーやモジュールのロードに関するエラーなどが記録されている可能性があります。


Apache の設定例

まず、ProxyPreserveHost On を有効にするための Apache の設定例です。これは通常、Apache の設定ファイル(httpd.conf または VirtualHost の設定ファイル)に記述します。

<VirtualHost *:80>
    ServerName www.example.com
    ProxyPreserveHost On
    ProxyPass / http://backend.example.internal:8080/
    ProxyPassReverse / http://backend.example.internal:8080/
</VirtualHost>

<VirtualHost *:80>
    ServerName another.example.com
    ProxyPreserveHost On
    ProxyPass / http://backend.example.internal:8080/
    ProxyPassReverse / http://backend.example.internal:8080/
</VirtualHost>

解説

  • ProxyPassReverse / http://backend.example.internal:8080/
    バックエンドサーバーからのリダイレクト応答などを適切に処理するために必要です。
  • ProxyPass / http://backend.example.internal:8080/
    / へのリクエストをバックエンドサーバーの http://backend.example.internal:8080/ にプロキシします。
  • ProxyPreserveHost On
    このディレクティブが、オリジナルのクライアントがリクエストした Host ヘッダーをバックエンドサーバー (http://backend.example.internal:8080/) にそのまま転送するように指示します。
  • ServerName www.example.com / ServerName another.example.com
    それぞれの仮想ホストに対応するドメイン名を指定しています。
  • <VirtualHost *:80>
    ポート 80 でリッスンする仮想ホストを定義しています。複数のドメイン (www.example.comanother.example.com) を同じ Apache サーバーで処理する例です。

この設定により、クライアントが www.example.com にアクセスした場合、バックエンドサーバーに送信される Host ヘッダーは www.example.com になります。同様に、another.example.com にアクセスした場合は、Host ヘッダーは another.example.com になります。

バックエンドサーバー側のプログラミング例

バックエンドサーバー側のアプリケーションは、このオリジナルの Host ヘッダーを利用して、リクエストされたドメインに基づいて異なる処理を行うことができます。以下に、一般的なプログラミング言語での Host ヘッダーの取得例を示します。

Python (Flask)

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def index():
    host = request.headers.get('Host')
    if host == 'www.example.com':
        return "Welcome to www.example.com!"
    elif host == 'another.example.com':
        return "Welcome to another.example.com!"
    else:
        return f"Unknown host: {host}"

if __name__ == '__main__':
    app.run(debug=True, port=8080)

解説

  • 取得した Host ヘッダーの値に基づいて、異なるメッセージを返しています。
  • request.headers.get('Host') で、リクエストの Host ヘッダーの値を取得しています。
  • Flask フレームワークを使用して簡単な Web アプリケーションを作成しています。

Node.js (Express)

const express = require('express');
const app = express();
const port = 8080;

app.get('/', (req, res) => {
  const host = req.headers.host;
  if (host === 'www.example.com') {
    res.send('Welcome to www.example.com!');
  } else if (host === 'another.example.com') {
    res.send('Welcome to another.example.com!');
  } else {
    res.send(`Unknown host: ${host}`);
  }
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

解説

  • 取得した Host ヘッダーの値に基づいて、異なるレスポンスを送信しています。
  • req.headers.host で、リクエストの Host ヘッダーの値を取得しています。
  • Express フレームワークを使用して簡単な Web アプリケーションを作成しています。

PHP

<?php
$host = $_SERVER['HTTP_HOST'];

if ($host === 'www.example.com') {
    echo "Welcome to www.example.com!";
} elseif ($host === 'another.example.com') {
    echo "Welcome to another.example.com!";
} else {
    echo "Unknown host: " . htmlspecialchars($host);
}
?>

解説

  • 取得した Host ヘッダーの値に基づいて、異なるメッセージを出力しています。
  • PHP のスーパーグローバル変数 $_SERVER['HTTP_HOST'] を使用して、リクエストの Host ヘッダーの値を取得しています。

これらの例からわかるように

ProxyPreserveHost On が Apache で設定されている場合、バックエンドアプリケーションはオリジナルのリクエスト先のドメイン名を Host ヘッダーを通じて知ることができます。これにより、一つのバックエンドアプリケーションで複数のドメインをホストし、それぞれのドメインに応じたコンテンツや振る舞いを提供することが可能になります。

  • ProxyPassProxyPassReverse の設定は、プロキシの基本的な動作を定義するために重要です。
  • バックエンドアプリケーションは、受け取った Host ヘッダーの値を信頼して処理する必要があります。セキュリティ上の考慮事項(Host ヘッダーインジェクションなど)にも注意が必要です。


代替的な方法

    • 仕組み
      Apache の mod_proxy モジュールや他のロードバランサー、リバースプロキシは、オリジナルの Host ヘッダーの値を X-Forwarded-Host というカスタム HTTP ヘッダーに追加することが一般的です。ProxyPreserveHost Off の場合や、手前に別のプロキシが存在する場合でも、このヘッダーを通じてオリジナルの Host 情報をバックエンドに伝えることができます。
    • Apache の設定例
      <VirtualHost *:80>
          ServerName www.example.com
          ProxyPass / http://backend.example.internal:8080/
          ProxyPassReverse / http://backend.example.internal:8080/
          RequestHeader set X-Forwarded-Host %{HTTP_HOST}
      </VirtualHost>
      
      <VirtualHost *:80>
          ServerName another.example.com
          ProxyPass / http://backend.example.internal:8080/
          ProxyPassReverse / http://backend.example.internal:8080/
          RequestHeader set X-Forwarded-Host %{HTTP_HOST}
      </VirtualHost>
      
      解説: RequestHeader set X-Forwarded-Host %{HTTP_HOST} ディレクティブは、リクエストをバックエンドに転送する際に、X-Forwarded-Host ヘッダーにオリジナルの Host ヘッダーの値を設定します。
    • バックエンド側のプログラミング例 (Python/Flask)
      from flask import Flask, request
      
      app = Flask(__name__)
      
      @app.route('/')
      def index():
          forwarded_host = request.headers.get('X-Forwarded-Host')
          if forwarded_host == 'www.example.com':
              return "Welcome to www.example.com (via X-Forwarded-Host)!"
          elif forwarded_host == 'another.example.com':
              return "Welcome to another.example.com (via X-Forwarded-Host)!"
          else:
              return f"Unknown host (via X-Forwarded-Host): {forwarded_host}"
      
      if __name__ == '__main__':
          app.run(debug=True, port=8080)
      
      解説: バックエンドアプリケーション側では、X-Forwarded-Host ヘッダーの値を取得して、オリジナルの Host 情報を利用します。
  1. カスタムヘッダーの利用

    • 仕組み
      X-Forwarded-Host 以外のカスタムヘッダーを定義して、オリジナルの Host ヘッダーの値をバックエンドに渡すことも可能です。
    • Apache の設定例
      <VirtualHost *:80>
          ServerName www.example.com
          ProxyPass / http://backend.example.internal:8080/
          ProxyPassReverse / http://backend.example.internal:8080/
          RequestHeader set Original-Host %{HTTP_HOST}
      </VirtualHost>
      
      <VirtualHost *:80>
          ServerName another.example.com
          ProxyPass / http://backend.example.internal:8080/
          ProxyPassReverse / http://backend.example.internal:8080/
          RequestHeader set Original-Host %{HTTP_HOST}
      </VirtualHost>
      
      解説: RequestHeader set Original-Host %{HTTP_HOST} ディレクティブは、Original-Host というカスタムヘッダーにオリジナルの Host ヘッダーの値を設定します。
    • バックエンド側のプログラミング例 (Node.js/Express)
      const express = require('express');
      const app = express();
      const port = 8080;
      
      app.get('/', (req, res) => {
        const originalHost = req.headers['original-host'];
        if (originalHost === 'www.example.com') {
          res.send('Welcome to www.example.com (via Original-Host)!');
        } else if (originalHost === 'another.example.com') {
          res.send('Welcome to another.example.com (via Original-Host)!');
        } else {
          res.send(`Unknown host (via Original-Host): ${originalHost}`);
        }
      });
      
      app.listen(port, () => {
        console.log(`Server listening at http://localhost:${port}`);
      });
      
      解説: バックエンドアプリケーション側では、設定したカスタムヘッダー (original-host) の値を取得して利用します。

ProxyPreserveHost On を使用しない理由と代替方法の利点

  • セキュリティ上の考慮
    ProxyPreserveHost On は、バックエンドサーバーが受け取った Host ヘッダーをそのまま信頼することを前提とします。場合によっては、意図しない Host ヘッダーが送られてくる可能性も考慮する必要があります。X-Forwarded-Host などのヘッダーを利用する場合、プロキシサーバーが信頼できることを前提に、これらのヘッダーを検証するロジックをバックエンドに追加することもできます。
  • 複雑なプロキシ構成
    複数のプロキシを経由する場合、ProxyPreserveHost だけではオリジナルのクライアントの Host ヘッダーが失われる可能性があります。X-Forwarded-Host は、プロキシサーバーが情報を追加していく標準的な方法であるため、より柔軟に対応できます。

注意点

  • セキュリティのため、X-Forwarded-Host などの X-Forwarded-* ヘッダーを信頼する場合は、リクエストが信頼できるプロキシからのものであることを検証する仕組みを導入することが推奨されます。
  • X-Forwarded-Host ヘッダーは、クライアントが送信した Host ヘッダーをそのまま伝えるとは限りません。通常は、リクエストを受けた最初のプロキシが設定します。複数のプロキシを経由する場合は、ヘッダーの値がカンマ区切りで複数含まれることがあります。
  • 代替方法を使用する場合、Apache の設定とバックエンドアプリケーションの両方で、使用するヘッダーの名前を一致させる必要があります。