你有没有碰到过这样的场景:前端上传一个几十 MB 的文件,浏览器直接弹出「413 Request Entity Too Large」,但后端接口明明没做大小限制?Nginx 413 是文件上传失败里最高频的拦路虎,根因几乎都出在 client_max_body_size 与层层代理的限制上。本文带你从现象到根因,把这条上传链路彻底讲透。
一、413 到底是什么:谁在拒绝你
HTTP 413 状态码的全称是 Request Entity Too Large,意思是「客户端发来的请求体太大,服务器拒绝接收」。关键点在于:这个错误通常由反向代理或网关返回,而不是你的应用代码。换句话说,请求根本还没进到 Spring Boot、Node 或 PHP,就在 Nginx 这一层被拦下了。这也是它和 502、504 最本质的区别——后两者是「上游出问题」,而 413 是「你太大了,我压根不收」。
| 状态码 | 含义 | 责任方 | 典型诱因 |
|---|---|---|---|
| 413 | 请求体过大被拒 | Nginx / 网关 | client_max_body_size 过小 |
| 502 | 网关拿到坏响应 | 上游应用 | 后端崩溃、端口错配 |
| 504 | 网关等待超时 | 上游应用 | 慢 SQL、死循环、阻塞 |
如果你看到的不是 413 而是 502/504,问题方向完全不同,可参考《502/504 线上故障排查》对症下药。
二、第一嫌疑人:client_max_body_size
Nginx 默认 client_max_body_size 是 1m(1 兆字节)。只要请求体超过这个值,Nginx 会直接返回 413,连后端影子都见不到。它的作用域是 http / server / location 三层,内层会覆盖外层。
# 放在 http / server / location 任意一层,作用域逐级覆盖
server {
listen 443 ssl;
server_name upload.example.com;
# 全局放宽到 50M
client_max_body_size 50m;
location /api/upload {
# 上传接口单独放宽到 200M
client_max_body_size 200m;
proxy_pass http://app:8080;
}
}
注意作用域继承:location 没写就继承 server,server 没写继承 http。很多人只在 http 层改了,结果被某个 server 块的默认值覆盖,排查半天以为没生效。所以定位时一定用 nginx -T 打印出完整生效配置来确认,而不是只盯着自己改的那一行。
三、代理链:你以为改了一处,其实层层设卡
真实生产环境不是「客户端 → Nginx → 应用」这么简单,而是一条代理链。每一层都可能限制请求体大小,任何一层没放开都会 413。
| 链路层级 | 限制项 | 配置位置 |
|---|---|---|
| 浏览器 / 前端 | 无硬限制,靠 JS 校验 | FormData / axios |
| CDN / 云 WAF | 单请求体上限(如阿里云 WAF 默认 32M) | 厂商控制台 |
| 负载均衡 SLB / ALB | 请求体上限 | 厂商控制台 |
| Nginx 反向代理 | client_max_body_size | nginx.conf |
| 应用前置 uwsgi / fastcgi | 各自的 body 限制 | uwsgi.ini 等 |
| 后端框架 | multipart 上限 | Spring / Tomcat 配置 |
# nginx 反代到 uwsgi 时,uwsgi 也有自己的限制
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
# uwsgi 模式下同样受 client_max_body_size 约束
client_max_body_size 200m;
}
这就是为什么改了 Nginx 还报错——uwsgi 或后端框架(如 Spring 的 multipart 上限、Tomcat 的 maxPostSize)还在悄悄拦。关于 Nginx 反代本身的完整配置姿势,可看《Nginx 反向代理完整配置:负载均衡 + HTTPS + 缓存优化》。
四、三步定位:到底是谁在拦
不要靠猜,用证据说话。下面这套流程能快速锁定责任层。
步骤 1:用 curl 原样复现(绕过浏览器缓存和前端 JS 校验):
# 准备一个 100M 的测试文件
dd if=/dev/zero of=big.bin bs=1M count=100
# 直接打 Nginx,看返回码
curl -s -o /dev/null -w "HTTP %{http_code}" -F "file=@big.bin" https://upload.example.com/api/upload
# 看响应头里有没有 Nginx 标记
curl -s -D - -o /dev/null -F "file=@big.bin" https://upload.example.com/api/upload | grep -i server
步骤 2:查 Nginx 错误日志,关键词非常直白:
# 报错长这样
# [error] 1234#0: *56 client intended to send too large body: 104857600 bytes
grep "too large body" /var/log/nginx/error.log
这条日志一出现,就坐实是 Nginx 的 client_max_body_size 拦的。如果日志里没有、但 CDN 返回 413,那多半是 CDN / WAF 层在拦。
步骤 3:逐层缩小。把 curl 直接打到「应用端口」跳过 Nginx,再打到「SLB 之后的 Nginx」跳过 CDN。哪一层开始返回 413,就是哪一层的问题,定位精确到分钟级。
五、不同场景怎么设才合理
不要图省事直接 client_max_body_size 0(不限制),那是给自己埋雷。按需、分层设置才是正解。
| 场景 | 建议做法 |
|---|---|
| 普通表单 / 头像 | 默认 1m~8m 足够,不用改 |
| 图片 / 文档上传 | 单接口放宽到 20m~50m |
| 视频 / 大附件 | 单接口 200m+,并配合前端分片 |
| 全站兜底 | 保持 http 层默认,只在 location 放开 |
// 前端分片上传,避免单次超大请求把任何一层打爆
async function uploadInChunks(file, chunkSize = 5 * 1024 * 1024) {
for (let start = 0; start < file.size; start += chunkSize) {
const blob = file.slice(start, start + chunkSize);
await fetch('/api/upload?offset=' + start, {
method: 'POST',
body: blob,
});
}
}
分片不仅绕开了单请求体上限,还能断点续传、并发加速,是视频类大文件上传的标准解法。
六、最容易踩的 5 个坑
1. 改了配置没 reload。 nginx -s reload 才生效,改完不 reload 等于没改,这是最高频的「我明明改了为啥还 413」。
2. 只改 server 漏了 location。 被更内层的默认值覆盖,用 nginx -T 看真实生效值。
3. 忘了 CDN / SLB 也有上限。 Nginx 放开了,云 WAF 或 SLB 还在拦,控制台也得同步调。
4. 设成 0 不设上限。 攻击者可以传超大文件把磁盘打满(排查思路可参考《磁盘 inode 耗尽排查》),务必加应用层校验和硬上限。
5. 后端框架上限忘了调。 Spring Boot 默认 multipart 1MB,Tomcat 的 maxPostSize 也要同步看,否则 Nginx 放开了后端照样拒。
七、一页排障清单
[ ] curl 原样复现,确认返回 413
[ ] 看响应 Server 头,判断是否 Nginx 返回
[ ] grep Nginx error.log 找 "too large body"
[ ] 检查 http / server / location 三层的 client_max_body_size
[ ] 检查 uwsgi / 后端框架上限
[ ] 检查 CDN / WAF / SLB 控制台的请求体上限
[ ] nginx -s reload 后重试
八、进阶:client_body_buffer_size 与 413 不是一回事
很多同学会把 client_max_body_size 和 client_body_buffer_size 搞混,其实二者职责完全不同。前者是「硬上限」,超过直接 413 拒绝;后者是「缓冲区大小」,决定请求体是先放内存还是先落磁盘(临时文件)。当请求体超过 client_body_buffer_size 时,Nginx 会把它写入 client_body_temp_path 指定的临时目录,而不是立刻报错。
这里有个隐藏雷区:如果那个临时目录权限不对、或者磁盘 inode 被占满(可参考《磁盘 inode 耗尽排查》),写入临时文件会失败,同样可能表现为上传异常。另外在反代场景下,proxy_request_buffering off 会关闭请求体缓冲、边收边转发,此时对 client_max_body_size 的限制依然有效,但绕开了本地落盘,适合大文件流式上传。
九、实战复盘:一次线上头像上传 413 的完整定位
讲一个真实案例。某天客服反馈:「用户换头像偶尔报上传失败,刷新重试又好了」。听起来像偶发,但我们按本文流程一查就定位了根因。
# 第一步:grep 错误日志,确认是 Nginx 在拦
grep "too large body" /var/log/nginx/error.log
# 发现有零星记录,但 body 大小只有几 MB,远没到 50m
# 第二步:nginx -T 看真实生效配置
nginx -T | grep -A2 "location /api/avatar"
# 发现该 location 没写 client_max_body_size,继承到了 http 层的 1m 默认值
真相大白:头像接口一开始用的是 1m 默认值,后来有人在 http 层统一改成了 50m,却忘了这个 location 因为历史上单独配过、并没有继承到新值。偶发的原因是——部分流量走的是另一个没单独配的 location,所以「重试就好」。
修复只需在对应 location 显式加上 client_max_body_size 20m;,nginx -s reload 后彻底消失。这个案例说明:用 nginx -T 看真实生效值,比相信自己改的那一行可靠得多。
Nginx 413 看似简单,背后却是一条需要逐层排查的代理链。记住一句话:先 curl 复现、再 grep 日志、最后逐层放开,别急着把 client_max_body_size 设成 0。把上传失败这类高频问题沉淀成清单,下次值班同学照着勾就行。




