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 管不到的了。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。