Nginxのclient_max_body_sizeディレクティブとcurlのdata-binaryオプション

ApacheとNginxをそれぞれ検証していたときにハマったのでメモしておく。

 

 

[リクエストボディサイズの設定]

Webサーバとして、もともとApacheを使っていたところを、Nginxに変更しようとした際に、ファイルのアップロード処理で問題が出た。

5MBまでのファイルアップロードができるアプリケーションだったのだが、それより小さいファイルでも「413 Request Entity Too Large」のエラーが帰ってくるようになった。

 

調べてみると、ApacheとNginxのデフォルト値の違いに起因する問題であることが分かった。

 

Apacheのリクエストボディサイズの設定項目は「LimitRequestBody」であり、デフォルト値は「0(無制限)」である。

httpd.apache.org

 

他方、Nginxのリクエストボティサイズの設定項目は「client_max_body_size」であり、デフォルト値は「1m(1MB)」であった。

nginx.org

 

client_max_body_sizeの説明を読むと次のように書かれている。

 

Sets the maximum allowed size of the client request body. If the size in a request exceeds the configured value, the 413 (Request Entity Too Large) error is returned to the client. Please be aware that browsers cannot correctly display this error. Setting size to 0 disables checking of client request body size.

 

簡単に訳すとこうなる。0を設定するとApacheのデフォルトと同じ挙動になるようだ。

クライアントリクエストボディの最大サイズを設定する。あるリクエストのサイズが設定されている値を超える場合、413(Request Entity Too Large)エラーが返る。ブラウザはこのエラーを正しく表示できないので注意。0に設定するとクライアントリクエストボディのサイズ確認を無効化する。

 

今回は5MBまでは許可して、それ以上の場合にはエラーにすればよいので、nginx.confで以下のように設定する。

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       8080;
        server_name  localhost;

        location / {
            client_max_body_size 5m;
            return 200;
        }
    }
}

 

[Curlでテストする]

利用するソフトウェアのバージョンは以下の通り。OSはmacOS Catalina 10.15.7を利用している。

$ nginx -v
nginx version: nginx/1.19.5

$ curl --version
curl 7.64.1 (x86_64-apple-darwin19.0) libcurl/7.64.1 (SecureTransport) LibreSSL/2.8.3 zlib/1.2.11 nghttp2/1.39.2
Release-Date: 2019-03-27
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS GSS-API HTTP2 HTTPS-proxy IPv6 Kerberos Largefile libz MultiSSL NTLM NTLM_WB SPNEGO SSL UnixSockets

 

client_max_body_sizeの設定をテストするために、まずは以下のコマンドでファイルを用意する。

$ dd if=/dev/urandom of=5m.dat bs=1m count=5 // 5Mのファイル
5+0 records in
5+0 records out
5242880 bytes transferred in 0.012535 secs (418264052 bytes/sec)

$ dd if=/dev/urandom of=6m.dat bs=1m count=6 // 6Mのファイル
6+0 records in
6+0 records out
6291456 bytes transferred in 0.016814 secs (374179758 bytes/sec)

 

次に、Nginxを起動する。brew install nginxでインストールしたので、nginxコマンドで起動できる。

起動できたかどうか、curlを利用して以下のように確認する。

$ curl -v http://localhost:8080
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Server: nginx/1.19.5
< Date: Sun, 25 Apr 2021 12:35:14 GMT
< Content-Type: application/octet-stream
< Content-Length: 0
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
* Closing connection 0

 

200 OKレスポンスが返ってきたので、きちんと起動できたことがわかる。

 

次に、client_max_body_sizeの設定が有効かどうか確認する。

まずは5MのファイルをPOSTしてみる。

$ curl --data @5m.dat -v http://localhost:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 2614750
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Server: nginx/1.19.5
< Date: Sun, 25 Apr 2021 12:40:04 GMT
< Content-Type: application/octet-stream
< Content-Length: 0
< Connection: keep-alive
< 
* We are completely uploaded and fine
* Connection #0 to host localhost left intact
* Closing connection 0

 

問題なくPOSTできた。しかし、送信したファイルサイズと一致するはずのContent-Lengthヘッダーの値を確認すると、2614750となっておりファイルサイズの5Mの半分しかない。

確認のため6MのファイルもPOSTしてみる。

$ curl --data @6m.dat -v http://localhost:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 3124110
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Server: nginx/1.19.5
< Date: Sun, 25 Apr 2021 12:39:11 GMT
< Content-Type: application/octet-stream
< Content-Length: 0
< Connection: keep-alive
< 
* We are completely uploaded and fine
* Connection #0 to host localhost left intact
* Closing connection 0

 

こちらも200 OKが返ってきてしまった。Content-Lengthヘッダーの値が3124110となっており、これも5Mに満たないためであろう。

試しに、11Mのファイルを作って同様に送信すると、この場合は413エラーが返ってきた。

$ curl --data @11m.dat -v http://localhost:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 5731722
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
< HTTP/1.1 413 Request Entity Too Large
< Server: nginx/1.19.5
< Date: Sun, 25 Apr 2021 12:53:44 GMT
< Content-Type: text/html
< Content-Length: 183
< Connection: close
< 
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx/1.19.5</center>
</body>
</html>
* Closing connection 0

 

[--data-binaryオプションを利用する]

curlのdataオプションについてググると、以下の記事がヒットした。

qiita.com

 

どうやら、--dataではなく--data-binaryオプションを利用してデータをPOSTすると、余計な変換処理が行われずに済みそうだということが分かった。

 

実際に試してみると、6MのファイルをPOSTした場合に以下のようにエラーとなった。

$ curl --data-binary @6m.dat -v http://localhost:8080/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 6291456
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
< HTTP/1.1 413 Request Entity Too Large
< Server: nginx/1.19.5
< Date: Sun, 25 Apr 2021 12:59:36 GMT
< Content-Type: text/html
< Content-Length: 183
< Connection: close
< 
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx/1.19.5</center>
</body>
</html>
* Closing connection 0

 

Content-Lengthヘッダーを確認すると、想定どおりファイルサイズと一致する値となっていた。

 

[まとめ]

  • Nginxでリクエストボディサイズを制限する場合はclient_max_body_sizeディレクティブを利用する
  • client_max_body_sizeディレクティブのデフォルト値は1m
  • curlでファイルをPOSTする場合は--data-binaryオプションを利用するとファイルサイズそのままで送信できる 

 

実は最初にcurlの出力しているContent-Lengthヘッダーの値がおかしいことに気が付くまでは、Nginxのバグかと考えて、慣れないC言語のコードを眺めたりしていたのは内緒。

github.com