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_sizeproxy_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>