小Cの已经记不起来的博客

Nginx 反向代理 文件上传 配置详解

前两天给一个文件服务套了层 Nginx 反向代理,挂到公网域名下面,小文件传得好好的,同事一传几百 MB 的视频就开始报错,浏览器里就给个 413,后端日志里连请求的影子都没有。看了一眼 Nginx 的 error.log,一行 client intended to send too large body,得,又是 client_max_body_size 这玩意儿。改完这个又陆陆续续碰到了上传中途断掉、临时目录没权限之类的坑,索性把整个过程和最终的配置记下来,省得下次部署又从头翻一遍。

413 的锅:client_max_body_size

Nginx 默认限制请求体大小是 1M,超过 1M 的上传请求会被直接拒掉,返回 413 Request Entity Too Large,请求根本到不了后端。这也解释了为什么后端日志里什么都没有,锅全在 Nginx 这一层。

改法很简单:

client_max_body_size 1024m;

这个指令放在 http、server、location 里都可以,作用范围依次缩小。我的习惯是只放在需要上传的 location 里,别的地方保持默认,免得整个站点的请求体限制都被放大。

另外要注意,如果你的架构里不止一层代理,比如前面还有一层 CDN 或者又套了一层 Nginx,那每一层都得改,漏了一层照样 413,这个真的很隐蔽,我就是被中间那台机器上的默认配置坑了一回...

大文件上传建议关掉 proxy_request_buffering

proxy_request_buffering 默认是 on,意思是 Nginx 会先把客户端上传的整个请求体完整收到本地(内存放不下的部分写到 client_body_temp_path 指向的临时目录),收完了才转发给后端。

小文件这样没什么问题,但传几个 G 的文件就有两个麻烦:一是 Nginx 机器上得腾出等量的磁盘空间放临时文件,二是后端要干等着 Nginx 收完才能开始处理,整条链路的时间被拉得很长。

所以上传接口建议直接关掉缓冲,让 Nginx 边收边转,流式透传:

proxy_request_buffering off;

关掉之后,如果客户端是用 Transfer-Encoding: chunked 传的,转发给后端时也是 chunked,后端得能处理这种情况,现在的主流框架基本都没问题。

顺带一提,如果你保留默认的缓冲,传大文件时报了 500,去 error.log 里看到一堆 open() "/var/lib/nginx/body/..." failed (13: Permission denied),那就是临时目录权限的问题,把目录 chown 给 Nginx 的运行用户,或者改一下 client_body_temp_path 就好了。

超时参数也得跟着改

文件大、带宽慢的时候,默认 60 秒的超时大概率不够用,表现就是传到一半连接被掐断。相关的有这么几个:

client_body_timeout   600s;  # 两次读取请求体之间的间隔超时
proxy_connect_timeout 60s;   # 和后端建立连接的超时
proxy_send_timeout    600s;  # 往后端发请求的超时
proxy_read_timeout    600s;  # 等后端响应的超时

数值按你实际的带宽估一下就行,不用无脑往大了调。

proxy_set_header 别漏了

反向代理的老三样,Host 和真实 IP,文件服务有时还会用 X-Forwarded-Proto 判断是不是 HTTPS:

proxy_set_header Host              $host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

CentOS 系的机器如果配置怎么改都是 502,先想想 SELinux,setsebool -P httpd_can_network_connect 1 一下没准儿就好了。

最后贴一份完整配置

实测可用的 location 块,上传相关的参数都凑齐了:

location /upload/ {
    proxy_pass http://127.0.0.1:8080;

    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    client_max_body_size     1024m;
    client_body_timeout      600s;
    proxy_request_buffering  off;
    proxy_connect_timeout    60s;
    proxy_send_timeout       600s;
    proxy_read_timeout       600s;
}

改完记得先 nginx -t 检查语法,再 nginx -s reload 热加载,别图省事直接重启,正在传的文件会全被干断。

怎么验证

最直接的办法就是用 curl 模拟上传,随便找个大点的文件:

curl -v -F "file=@bigfile.zip" https://your-domain.com/upload/

-v 会把请求和响应的详细信息都打出来,返回 200 就说明整条链路通了。如果还是 413,回头检查是不是有不止一层 Nginx;如果是传到一半断掉,重点看 proxy_read_timeout,以及后端自己的上传限制,比如 Spring Boot 里还有个 spring.servlet.multipart.max-file-size,这些就是 Nginx 管不到的了。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放