Apacheの「Expect: 100-continue」問題を解決!Proxy100Continue設定とトラブルシューティング
Proxy100Continue
は、Apache HTTP Serverのmod_proxy
モジュールで使用されるディレクティブです。これは、クライアントが送信する「Expect: 100-continue」HTTPヘッダーの扱いを制御します。
HTTP/1.1の「Expect: 100-continue」ヘッダーとは?
HTTP/1.1では、クライアントが大きなリクエストボディ(例:大きなファイルをアップロードする際のPOSTやPUTリクエスト)を送信する前に、サーバーにそのリクエストを受け付ける準備があるかどうかを問い合わせるメカニズムとして「Expect: 100-continue」ヘッダーを使用することがあります。
このヘッダーを送信することで、クライアントはまずリクエストヘッダーだけを送り、サーバーが「100 Continue」という中間的なステータスコードを返した場合にのみ、続けてリクエストボディを送信します。もしサーバーが「417 Expectation Failed」などのエラーコードを返したり、リクエストを拒否するような応答を返したりした場合は、クライアントは無駄に大きなリクエストボディを送信せずに済みます。
Proxy100Continueの役割
mod_proxy
を介してリバースプロキシとして機能するApacheサーバーは、クライアントからのリクエストをバックエンドサーバー(オリジンサーバー)に転送します。この際、クライアントが「Expect: 100-continue」ヘッダーを含んでいる場合、Apacheは次のいずれかの動作をとる必要があります。
- バックエンドサーバーに「Expect: 100-continue」ヘッダーを転送する。
- Apache自身が「100 Continue」応答をクライアントに返し、バックエンドサーバーには転送しない。
- 「417 Expectation Failed」をクライアントに返す。
Proxy100Continue
ディレクティブは、この1番目の動作、つまり「Expect: 100-continue」ヘッダーをバックエンドサーバーに転送するかどうかを制御します。
-
Proxy100Continue Off
クライアントから受け取った「Expect: 100-continue」ヘッダーをバックエンドサーバーに転送しません。代わりに、Apache自身がクライアントに「100 Continue」応答を返します。これにより、バックエンドサーバーは「Expect: 100-continue」の処理について考慮する必要がなくなります。 -
Proxy100Continue On (デフォルト)
クライアントから受け取った「Expect: 100-continue」ヘッダーを、バックエンドサーバーに転送します。バックエンドサーバーは、そのヘッダーに基づいて「100 Continue」応答を返すか、他のエラーを返すかを決定します。この応答はApacheを介してクライアントに返されます。これはApache HTTP Server 2.4.40以降で利用可能です。
なぜこの設定が必要なのか?
一部のクライアントやバックエンドサーバーの実装は、「Expect: 100-continue」ヘッダーの扱いに癖がある場合があります。
-
バックエンドサーバーが「100 Continue」に対応していない、または適切に処理しない
バックエンドサーバーが「Expect: 100-continue」ヘッダーを正しく処理できない場合、またはその動作が予期せぬ結果を引き起こす場合、Proxy100Continue Off
に設定することでApacheが間に立って「100 Continue」を処理し、バックエンドサーバーには通常の(Expect
ヘッダーのない)リクエストを送信できます。 -
クライアントが「100 Continue」を待たずにボディを送信してしまう
一部のクライアントは「Expect: 100-continue」ヘッダーを送信しても、サーバーからの「100 Continue」応答を待たずにリクエストボディの送信を開始してしまうことがあります。この場合、ApacheがProxy100Continue On
でバックエンドに転送していると、バックエンドが応答を返す前にクライアントがボディを送り終えてしまい、タイミングの問題でエラー(例: 417 Expectation Failed)が発生することがあります。
Proxy100Continue
ディレクティブは、「Expect: 100-continue」HTTP ヘッダーの処理方法を制御するため、この設定が原因で発生する問題は主に、クライアントとバックエンドサーバー間の通信のタイミングや「Expect」ヘッダーの解釈の不一致に起因します。
エラー:HTTP 417 Expectation Failed
これは最も一般的なエラーです。クライアントが「Expect: 100-continue」ヘッダーを送信したにもかかわらず、Apache またはバックエンドサーバーがその期待に応えられなかった場合に発生します。
原因
- ネットワーク機器やロードバランサーが「Expect: 100-continue」ヘッダーを妨害している
間に挟まっているファイアウォールやロードバランサーが、このヘッダーを正しく転送しなかったり、独自の処理を行ったりすることで問題を引き起こすことがあります。 - クライアントが「100 Continue」応答を待たずにボディを送信してしまう
一部のクライアント実装では、「Expect: 100-continue」ヘッダーを送信しても、サーバーからの「100 Continue」応答を待たずにリクエストボディの送信を開始してしまうことがあります。この場合、ApacheがProxy100Continue On
でバックエンドに転送していると、バックエンドが応答を返す前にクライアントがボディを送り終えてしまい、タイミングの問題でエラーが発生することがあります。 - バックエンドサーバーが「Expect: 100-continue」ヘッダーに対応していない、または正しく処理しない
Proxy100Continue On
(デフォルト)の場合、Apacheは「Expect: 100-continue」ヘッダーをそのままバックエンドサーバーに転送します。バックエンドがこのヘッダーを理解しないか、適切な「100 Continue」応答を返さない場合、クライアントは「417 Expectation Failed」を受け取ることがあります。
トラブルシューティング
- ネットワーク経路の確認
Apacheとバックエンドサーバーの間にロードバランサーやCDNなどがある場合、それらが「Expect: 100-continue」ヘッダーをどのように処理しているか確認します。 - クライアント側の挙動を確認・調整
可能であれば、クライアントアプリケーションの「Expect: 100-continue」ヘッダーの送信挙動を確認し、必要であれば無効化または調整を検討します(例: .NETのServicePointManager.Expect100Continue = false
)。 - バックエンドサーバーのログを確認
バックエンドサーバー(Tomcat、Node.jsなど)のログを確認し、417 Expectation Failed
が発生しているか、または「Expect」ヘッダーに関連する警告やエラーがないか確認します。バックエンドサーバー側で「Expect: 100-continue」の処理に関する設定や対応が必要な場合があります。 - Proxy100Continue Off の試行
Apacheが「Expect: 100-continue」ヘッダーを受け取った際に、バックエンドに転送せずにApache自身が「100 Continue」応答をクライアントに返すように設定します。これにより、バックエンドサーバーは「Expect」ヘッダーの処理を意識する必要がなくなります。
Apacheを再起動(またはリロード)して、問題が解決するか確認してください。<VirtualHost *:80> ProxyPreserveHost On ProxyRequests Off ProxyPass / http://backend.example.com/ ProxyPassReverse / http://backend.example.com/ Proxy100Continue Off # この行を追加または変更 </VirtualHost>
エラー:大きなファイルアップロード時のタイムアウトや切断
「Expect: 100-continue」のハンドシェイクがうまく機能しないと、大きなリクエストボディの送信が開始されず、クライアント側でタイムアウトが発生したり、接続が予期せず切断されたりすることがあります。
原因
- Apacheが「100 Continue」を返すべきタイミングで返せていない、またはバックエンドからの応答が遅い。
- 上記
417 Expectation Failed
と同様の原因が考えられます。クライアントが「100 Continue」を待っていても、それが返ってこないため、ボディの送信が開始されない。
トラブルシューティング
- バックエンドサーバーのパフォーマンス確認
バックエンドサーバー自体がリクエストの処理や「100 Continue」の応答に時間がかかっている可能性もあります。バックエンド側のパフォーマンスやログを確認してください。 - ProxyTimeout の確認と調整
ProxyTimeout
ディレクティブは、Apacheがバックエンドサーバーからの応答を待つ最大時間を定義します。もしバックエンドが「100 Continue」応答を返すのに時間がかかっている場合、このタイムアウトを調整する必要があるかもしれません。ProxyTimeout 300 # 例: 300秒に設定
- Proxy100Continue Off の試行
これにより、Apacheがクライアントに速やかに「100 Continue」応答を返すため、クライアントはボディの送信を速やかに開始できます。
エラー:Apacheのログに特に関連するエラーが出ないが、問題が発生する
場合によっては、Apacheのエラーログに直接的なmod_proxy: Proxy100Continue
に関連するエラーが出ないにもかかわらず、クライアント側で問題が発生することがあります。
原因
- 問題がクライアントとApache間の初期接続で発生しているか、またはバックエンドサーバーの内部的な問題である可能性があります。
- 「Expect: 100-continue」の動作はHTTPプロトコルの中間的なハンドシェイクであるため、エラーとして記録されない場合があります。
- TCPダンプ(Wiresharkなど)の取得
もし可能であれば、クライアントとApache間、およびApacheとバックエンドサーバー間のネットワークトラフィックをキャプチャし、HTTPヘッダーやデータ転送のシーケンスを詳細に分析します。これにより、「Expect: 100-continue」ヘッダーが正しく送信・受信されているか、また「100 Continue」応答が期待通りに返されているかを確認できます。 - アクセスログの確認
アクセスログに、対象となるリクエストが記録されているか、どのようなステータスコードが返されているかを確認します。特に、リクエストサイズが0バイトでステータスコードが異常な場合(例: 400 Bad Requestなど)は、ボディの送信が開始されていない可能性があります。 - Apacheのログレベルを上げる
LogLevel debug
を設定することで、mod_proxy
に関するより詳細なデバッグ情報を取得できる場合があります。これにより、リクエストの転送状況や応答のタイミングなどを詳しく確認できます。
注意:LogLevel debug
debug
レベルは非常に多くのログが出力されるため、一時的なトラブルシューティング目的でのみ使用し、問題解決後は元に戻すことを推奨します。
全体的なトラブルシューティングのヒント
- Apacheのバージョンを確認
Proxy100Continue
ディレクティブはApache 2.4.40 以降で導入されたため、古いバージョンでは利用できません。また、最新の安定版を使用することで、既知のバグが修正されている可能性があります。 - 設定ファイルの構文チェック
apachectl configtest
またはhttpd -t
コマンドを使用して、設定ファイルに構文エラーがないか確認します。 - 関連モジュールの有効化
mod_proxy
とmod_proxy_http
が適切にロードされていることを確認してください。LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so
- 段階的なデバッグ
複雑な環境では、問題を単純化して切り分けます。例えば、バックエンドサーバーに直接アクセスして問題が再現するかどうかを確認することで、Apacheプロキシ層に問題があるのか、バックエンド自体に問題があるのかを切り分けられます。
Proxy100Continue
ディレクティブは、Apache がリバースプロキシとして機能する際に、クライアントからの「Expect: 100-continue」HTTP ヘッダーをどのように扱うかを制御します。この設定は、主に httpd.conf
やバーチャルホストの設定ファイル(例: sites-available/your-site.conf
)内に記述されます。
Proxy100Continue On (デフォルトの挙動)
Apache 2.4.40 以降では、Proxy100Continue
のデフォルト値は On
です。これは、クライアントから「Expect: 100-continue」ヘッダーを受け取った場合、Apache がそのヘッダーをバックエンドサーバーに転送することを意味します。バックエンドサーバーは、そのヘッダーに基づいて「100 Continue」応答を返すか、他の応答を返すかを決定します。
この設定は、特に何も記述しない場合に適用されます。明示的に記述する場合は以下のようになります。
# 必要なモジュールの読み込み(通常はコメントを外すか、自動でロードされます)
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
<VirtualHost *:80>
ServerName yourdomain.com
ProxyPreserveHost On
# クライアントからの "Expect: 100-continue" ヘッダーをバックエンドに転送します。
# これはデフォルトの挙動ですが、明示的に記述することも可能です。
Proxy100Continue On
# プロキシ設定
ProxyPass / http://backend.example.com:8080/
ProxyPassReverse / http://backend.example.com:8080/
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log combined
</VirtualHost>
使用例のシナリオ
- クライアントとバックエンドサーバー間の「Expect: 100-continue」ハンドシェイクを完全に維持したい場合。
- バックエンドサーバーが「Expect: 100-continue」ヘッダーを正しく処理し、独自のビジネスロジックに基づいて「100 Continue」応答を返したい場合。
Proxy100Continue Off
この設定では、Apache がクライアントから「Expect: 100-continue」ヘッダーを受け取った場合、そのヘッダーをバックエンドサーバーに転送しません。代わりに、Apache 自身がクライアントに「100 Continue」応答を返します。これにより、クライアントはリクエストボディの送信を開始でき、バックエンドサーバーには「Expect: 100-continue」ヘッダーがない状態でリクエストが転送されます。
# 必要なモジュールの読み込み
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
<VirtualHost *:80>
ServerName yourdomain.com
ProxyPreserveHost On
# クライアントからの "Expect: 100-continue" ヘッダーをバックエンドに転送せず、
# Apache 自身が "100 Continue" 応答をクライアントに返します。
Proxy100Continue Off
# プロキシ設定
ProxyPass / http://backend.example.com:8080/
ProxyPassReverse / http://backend.example.com:8080/
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log combined
</VirtualHost>
使用例のシナリオ
- Apache がクライアントとの間の「100 Continue」ハンドシェイクを完全に制御し、バックエンドサーバーの負荷や複雑さを軽減したい場合。
- クライアントが「100 Continue」応答を待たずにボディの送信を開始してしまう(またはそのように設計されている)場合に、通信の安定性を確保したい場合。
- バックエンドサーバーが「Expect: 100-continue」ヘッダーを適切に処理できない、または処理しない場合に、
417 Expectation Failed
エラーを回避したい場合。
mod_headers を使って Expect ヘッダーを削除する(代替案)
Proxy100Continue
ディレクティブがない古い Apache バージョンを使用している場合や、より厳密に Expect
ヘッダーを削除したい場合は、mod_headers
モジュールを使用してこのヘッダーを削除するという方法も考えられます。
# 必要なモジュールの読み込み
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule headers_module modules/mod_headers.so # mod_headers をロード
<VirtualHost *:80>
ServerName yourdomain.com
ProxyPreserveHost On
# クライアントから受け取った "Expect" ヘッダーを削除します。
# これにより、バックエンドサーバーには "Expect" ヘッダーが転送されません。
# この設定は Proxy100Continue Off と似た効果をもたらしますが、
# Apacheが "100 Continue" 応答を返すかどうかは制御しません。
RequestHeader unset Expect early
# プロキシ設定
ProxyPass / http://backend.example.com:8080/
ProxyPassReverse / http://backend.example.com:8080/
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log combined
</VirtualHost>
注意点
- クライアントが「Expect: 100-continue」を送信したにもかかわらず、サーバーから何も応答がないままボディを送信し始めた場合、クライアント側で予期せぬ挙動(タイムアウトなど)が発生する可能性もゼロではありません。
- この方法は、「Expect: 100-continue」以外の
Expect
ヘッダー(ほとんど使われませんが)も削除します。 RequestHeader unset Expect early
は、リクエストの初期段階でヘッダーを削除するため、Apache 自身がその後の処理で「Expect: 100-continue」を認識しなくなります。
設定後の適用方法
設定ファイルを変更した後は、Apache HTTP Server を再起動またはリロードする必要があります。
- 設定のテストのみ行う場合
sudo apachectl configtest
- System V init を使用している場合 (古いシステム)
sudo service apache2 restart # または httpd restart
- Systemd を使用している場合 (CentOS 7+, Ubuntu 16.04+など)
sudo systemctl restart apache2 # または httpd
Proxy100Continue
ディレクティブは、Apache 2.4.40 以降で導入された比較的新しい機能です。それ以前のバージョンや、より細かい制御が必要な場合、あるいは特定のクライアントやバックエンドの挙動に対応するために、いくつかの代替手段が考えられます。
mod_headers を使用して Expect ヘッダーを削除する
これは最も一般的な代替方法であり、Proxy100Continue Off
と同様の効果をもたらします。mod_headers
モジュールは、リクエストやレスポンスのHTTPヘッダーを操作するための非常に強力なツールです。
目的
クライアントから送信された「Expect: 100-continue」ヘッダーを、Apacheがバックエンドサーバーに転送する前に削除します。これにより、バックエンドサーバーは「Expect」ヘッダーを考慮する必要がなくなります。Apacheは通常、Expect
ヘッダーが削除されたことを認識し、自身で「100 Continue」応答をクライアントに返します。
設定例
# 必要なモジュールの読み込み
LoadModule headers_module modules/mod_headers.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
<VirtualHost *:80>
ServerName yourdomain.com
ProxyPreserveHost On
# クライアントから受け取った "Expect" ヘッダーを、リクエストの早い段階で削除します。
# これにより、バックエンドサーバーにはこのヘッダーが転送されません。
RequestHeader unset Expect early
# プロキシ設定
ProxyPass / http://backend.example.com:8080/
ProxyPassReverse / http://backend.example.com:8080/
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log combined
</VirtualHost>
利点
Expect
ヘッダーを確実にバックエンドから隠蔽できます。Proxy100Continue
ディレクティブがない古いApacheバージョンでも使用できます。
考慮事項
Proxy100Continue Off
とは異なり、Apacheが「100 Continue」応答を返すかどうかを明示的に制御するわけではありません(ただし、通常は返します)。クライアントの挙動によっては、ボディの送信が開始されないなどの問題が発生する可能性もゼロではありません。
クライアントサイドでの「Expect: 100-continue」の無効化
もしアプリケーション開発者としてクライアント側のコードを制御できるのであれば、「Expect: 100-continue」ヘッダーの送信自体を停止するのが最も直接的な解決策です。これにより、Apacheのプロキシ層での複雑な処理を回避できます。
多くのHTTPクライアントライブラリやフレームワークには、この機能を無効にするための設定があります。
プログラミング言語ごとの例
-
Python (requests)
requests
ライブラリは通常、大きなボディを送信する場合に自動的に「Expect: 100-continue」を処理します。もし問題が発生するなら、カスタムヘッダーとしてExpect
を空にする、またはその挙動を制御する低レベルなライブラリを使うことになります。import requests headers = {'Expect': ''} # Expect ヘッダーを空に設定して抑制 data = b'a very large amount of data...' try: response = requests.post('http://yourdomain.com/upload', data=data, headers=headers) response.raise_for_status() # HTTPエラーが発生した場合に例外を投げる print("Upload successful:", response.text) except requests.exceptions.RequestException as e: print(f"Error during upload: {e}")
-
Java (HttpURLConnection / HttpClient)
システムプロパティまたは HttpClient ビルダーで設定します。// システムプロパティとして設定 (アプリケーション全体に影響) System.setProperty("http.keepAlive", "false"); // Keep-Aliveを無効にするとExpect: 100-continueも抑制される傾向 System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); // 必要に応じて System.setProperty("http.expect.timeout", "0"); // タイムアウトを0に設定して即時送信を促す(推奨されないが効果はある場合も) // HttpClient (Java 11+) の場合、通常はExpect: 100-continueを自動で処理するため、 // 明示的な設定は不要なことが多いですが、問題がある場合はヘッダーを直接操作する // または、低いレベルのHTTPクライアントライブラリを使用する HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://example.com/upload")) .POST(HttpRequest.BodyPublishers.ofByteArray(largeData)) // Expectヘッダーを明示的に追加しない、または削除する .setHeader("Expect", "") // 空文字列を設定して抑制を試みる .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
古い
HttpURLConnection
の場合、http.keepAlive
システムプロパティが影響する場合があります。
利点
- 不要なHTTPハンドシェイクを完全に排除できるため、通信オーバーヘッドがわずかに減少する可能性があります。
- Apache側の設定に依存せず、クライアントの挙動を直接制御できます。
考慮事項
- 「Expect: 100-continue」は、クライアントが大きなデータを無駄に送信する前にサーバーがリクエストを拒否できるという本来の利点を失うことになります。セキュリティポリシーや帯域幅の節約が重要な場合は、この利点を考慮する必要があります。
- 全てのクライアント(特にサードパーティのクライアント)のコードを制御できるとは限りません。
ロードバランサーやCDNでの設定変更
もしApacheの前にロードバランサー(例: Nginx, HAProxy, AWS ELB/ALB, Azure Application Gateway など)やCDNが存在する場合、それらのデバイスが「Expect: 100-continue」ヘッダーの処理を制御している可能性があります。
多くのモダンなロードバランサーは、「Expect: 100-continue」ヘッダーの「Pass」「Ignore」「Silent Pass」「Not Allow」といったモードを提供しています。
- Not Allow
「417 Expectation Failed」をクライアントに返します。 - Silent Pass
ヘッダーを削除してバックエンドに転送せず、ロードバランサー自身が「100 Continue」をクライアントに返します(Proxy100Continue Off
と同等)。 - Ignore
ヘッダーを削除してバックエンドに転送しませんが、クライアントには何も応答しません(クライアントがタイムアウトする可能性があります)。 - Pass
ヘッダーをバックエンドにそのまま転送します(Proxy100Continue On
と同等)。
トラブルシューティング/設定方法
Silent Pass
モード(もしあれば)は、多くの場合、Proxy100Continue Off
と同様に問題を解決できる可能性があります。- 使用しているロードバランサーやCDNのドキュメントを確認し、「Expect: 100-continue」の処理に関する設定オプションを探します。
利点
- ロードバランサーが複数のバックエンドサーバーを持つ場合、一元的に管理できます。
- Apacheレイヤーでの設定変更が不要になります。
考慮事項
- 複雑な環境では、問題の切り分けが難しくなる場合があります。
- ロードバランサー/CDNの機能や設定に依存します。