Nginx client_max_body_size 不生效的奇怪问题

最近在配置 Nginx 的 client_max_body_size 的时候遇到了一个非常奇怪的现象,明明配置了这个设置但是却并没有生效。具体配置是这样的:

http {
    client_max_body_size 8k;
    #...
    server {
        location /a {
            proxy_pass http://a.b.c.d:aaaa;
        }
        location /a/upload {
            client_max_body_size 20m;
            proxy_pass http://a.b.c.d:aaaa;
        }
        #...
    }
    #...
    proxy_intercept_errors on;
    error_page 400 404 405 406 /error_page.html;
    error_page 500 501 502 503 504 /error_page.html;
    location /error_page.html {
        internal;
        #...
    }
}

在实际测试中发现,/a/upload 这个接口上传文件还是会失败,而且大小确实是符合限制的。为了确认不是文件大小的问题,这里还尝试了使用了 client_max_body_size 0; 直接关掉限制,可是还是不起作用。查了很多日志都没找到问题,唯一比较奇怪的是,在错误日志中记录的 request method 与实际不符,日志中 $request_method  记录的是 GET,但是客户端上送的确实是标准的 POST,$request  变量打出来的也是 ‘POST /a/upload’。

万般无奈之下只好上 debug 日志,果然找到了问题的根本原因:

[debug] 4593#0: *1685438 http special response: 500, "/a/upload"
[debug] 4593#0: *1685438 internal redirect: "/error_page.html?"

这里可以在 debug 日志中看到,上传接口其实是调用了后端服务的,但是后端返回了 http 500 状态码。这时候,由于 proxy_intercept_errors  和 error_page  的设置,该请求被内部转发到了 /error_page.html  ,但是这个 location 并没有特别配置 client_max_body_size  ,所以使用的是 http 层的设置 8k。而请求中的文件大小显然是大于 8k 的,于是在这里返回了 413 错误。

找到问题之后就很好解决了,只需要在 /error_page.html  这个 location 中也配置 client_max_body_size  就可以了。

这里也体现出了 nginx 一个比较麻烦的问题,就是内部的 error_page 的处理。要记住的一点就是,在不特殊配置的情况下,error_page 也会继承 http 层的各种配置。也就是说,会有很多蛋疼的情况出现:

  1. http 层配置了 ip 白名单限制,但是又不想用默认的 403 页面,于是自定义了 403 的 error_page,发现不生效。原因就是自定义的 error_page 也继承 http 层的白名单限制,导致不在白名单的用户访问这个 403 页面也被拒绝了。解决方法是单独给 403 页面配置 allow all。
  2. 其他的很多参数配置,比如 client_max_body_size 之类会带来 4xx 错误的配置,都有可能由于 error_page 继承 http 层配置,导致奇怪的问题。