nginx でリバースプロキシする際は HTTP レスポンスヘッダーのサイズに注意
nginx で Web サーバーをリバースプロキシする際は以下に注意が必要です。 (nginx 1.8.0 と 1.9.4 で確認)
- リバースプロキシ先からの HTTP レスポンスヘッダーのサイズが
proxy_buffer_size
の設定値を超えると 502 Bad Gateway エラーとなる
その場合のエラーログは次の通りです。
エラーログ例
2015/08/24 00:34:03 [error] 3672#4680: *6 upstream sent too big header while reading response header from upstream, ・・・
このエラーが発生した場合は、proxy_buffer_size
の値をレスポンスヘッダーのサイズより大きくする必要があります。
proxy_buffer_size
のデフォルト値は 4KB か 8KB に設定されているようですので、通常の Web アプリケーションでお目にかかる事はないかもしれません。
また、proxy_buffering
の値 (on / off) に関わらず発生します。
nginx ソース確認
実際にどうなっているのか nginx 1.9.4 のソースを見てみました。
src/http/ngx_http_upstream.c の ngx_http_upstream_process_header
関数で upstream sent too big header
のエラーログを出力しています。
受信したレスポンスヘッダーがバッファに収まりきらなかった ※ 場合に upstream sent too big header
のログを出力しエラーとしているようです。
※ レスポンスヘッダーを受信し終わっていないのにバッファが終端に到達
src/http/ngx_http_upstream.c の該当ソース
static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) { ・・・ if (u->buffer.start == NULL) { u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size); ・・・ u->buffer.pos = u->buffer.start; u->buffer.last = u->buffer.start; u->buffer.end = u->buffer.start + u->conf->buffer_size; ・・・ } for ( ;; ) { n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last); ・・・ rc = u->process_header(r); if (rc == NGX_AGAIN) { if (u->buffer.last == u->buffer.end) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream sent too big header"); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; } continue; } break; } ・・・ }
また、上記で使用しているバッファサイズ u->conf->buffer_size
は proxy_buffer_size
の設定値を使用していると思われます。
src/http/modules/ngx_http_proxy_module.c の該当ソース
・・・ { ngx_string("proxy_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, upstream.buffer_size), NULL }, ・・・
検証
最後に、簡単なサンプル Web アプリケーションを作って検証してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20150828/
nginx 設定ファイル
proxy_buffer_size
を 1KB に減らし、http://127.0.0.1:8081
へリバースプロキシを行う設定 (以下) で nginx を起動しておきます。
nginx.conf
events { } http { upstream ap { server 127.0.0.1:8081; } server { listen 8080; location / { proxy_pass http://ap; proxy_buffer_size 1k; } } }
サンプル Web サーバーアプリケーション
次のような単純な Web サーバー (Undertow を使用) を実行するスクリプトを用意しました。
- レスポンスヘッダー
TEST
へ設定する t の文字数を実行時引数で指定 (例えば't' * 5
の結果はttttt
) - レスポンスボディに sample という文字列を返す
server_sample.groovy
@Grab('io.undertow:undertow-core:1.3.0.Beta9') import io.undertow.Undertow import io.undertow.server.HttpHandler import io.undertow.util.Headers import io.undertow.util.HttpString // t の文字数 def size = args[0] as int def server = Undertow.builder().addListener(8081, 'localhost').setHandler( { ex -> // レスポンスヘッダー ex.responseHeaders .put(Headers.CONTENT_TYPE, 'text/plain') .put(new HttpString('TEST'), 't' * size) // レスポンスボディ ex.responseSender.send('sample') } as HttpHandler ).build() server.start()
上記スクリプトが返すレスポンスヘッダーは以下のようになります。
レスポンスヘッダー例
$ curl -I http://localhost:8081/ HTTP/1.1 200 OK TEST: ttttttttttttttttttttttt・・・ Connection: keep-alive Content-Type: text/plain Content-Length: 6 Date: Mon, 24 Aug 2015 06:04:08 GMT
動作検証1
まずは、t の数 800 で server_sample.groovy を実行してみます。
レスポンスヘッダーが 1KB を超えないはずなので正常に結果が返ってくるはずです。
> groovy server_sample.groovy 800 ・・・
nginx へアクセスしてみると問題なく sample
という文字列が返ってきました。
$ curl http://localhost:8080/ sample
動作検証2
次は 1100 で実行してみます。
1KB を超えるので 502 エラーとなるはずです
> groovy server_sample.groovy 1100 ・・・
nginx へアクセスしてみると想定通り 502 Bad Gateway が返ってきました。
$ curl http://localhost:8080/ <html> <head><title>502 Bad Gateway</title></head> <body bgcolor="white"> <center><h1>502 Bad Gateway</h1></center> <hr><center>nginx/1.9.4</center> </body> </html>