适用场景

业务通过 Nginx 上传图片、安装包、备份文件或导入数据时,小文件正常,大文件刚提交便收到 413 Request Entity Too Large。应用日志没有请求记录,客户端通常只看到一个 Nginx 错误页。

本文以“上传 80 MB 压缩包失败、10 MB 图片正常”为例,说明怎样确认拦截层、放开正确范围,并避免把上传接口变成磁盘与带宽攻击入口。

先理解:413 是谁返回的

HTTP 413 表示请求体超过接收方允许的大小。它不一定由应用返回:在常见的反向代理架构中,Nginx 会先读取请求头,并依据 Content-Length 或读取到的请求体执行大小检查。若检查失败,请求不会转发给 upstream,因此应用访问日志、APM 和业务异常都可能毫无痕迹。

上传链路可能有多层限制:

  1. CDN、WAF 或负载均衡的请求体限制;
  2. 最外层 Nginx 的 client_max_body_size
  3. 内层网关或 Ingress 的同类配置;
  4. 应用框架、对象存储 SDK 或业务规则的文件大小限制。

不要看到 413 就只改应用配置。先确认响应来自哪一层,才能避免改完仍然失败。

现场排查步骤

1. 用可控大小的文件复现

在测试环境或已授权目录创建不同大小的文件,并记录临界值:

truncate -s 20M upload-20m.bin
truncate -s 80M upload-80m.bin

curl -i -F "file=@upload-20m.bin" https://upload.example.com/api/files
curl -i -F "file=@upload-80m.bin" https://upload.example.com/api/files

-i 会输出响应头。注意 ServerViaX-Request-ID 等字段:若 Server: nginx 且应用没有对应请求日志,优先检查 Nginx 或其前面的代理。生产环境应使用脱敏测试文件,并限制测试次数,避免占满带宽。

2. 查看 Nginx 错误日志和生效配置

sudo tail -n 100 /var/log/nginx/error.log
sudo nginx -T | grep -n -E 'client_max_body_size|client_body_(buffer_size|temp_path)'

典型错误日志是:

client intended to send too large body: 83886080 bytes

nginx -T 会展开主配置及 include 的文件,比只查看 nginx.conf 更可靠。重点确认该指令所在的 httpserverlocation 块;配置越靠近具体 location,优先级越高。

3. 排除 Ingress 和上游网关

Kubernetes 使用 NGINX Ingress 时,限制常在 Ingress 注解中,而不是工作负载容器内的 Nginx:

kubectl -n production get ingress upload-api -o yaml
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --since=15m | grep 'too large body'

确认是否存在 nginx.ingress.kubernetes.io/proxy-body-size。如果请求经过 CDN/WAF,也要在其控制台或访问日志中确认产品限制;浏览器直接命中源站成功、经过域名失败,往往说明问题在源站之前。

安全修复:只放开上传接口

不要在全局直接设置 client_max_body_size 0。无限制会让任意请求体占用代理的磁盘、内存和带宽。建议将上限设为业务允许的最大文件加上 multipart 开销,并只应用到上传路径:

server {
    listen 443 ssl http2;
    server_name upload.example.com;

    # 其他接口继续使用较小的默认限制
    client_max_body_size 2m;

    location = /api/files {
        # 允许最大 200 MB 的单文件上传
        client_max_body_size 210m;
        client_body_timeout 60s;

        proxy_pass http://file_api;
        proxy_request_buffering on;
        proxy_read_timeout 120s;
    }
}

client_max_body_size 控制 Nginx 接受的整个 HTTP 请求体,不是业务文件的精确大小;表单字段和 multipart 边界也会占用空间。client_body_timeout 限制两次读取请求体之间的等待时间,能减少慢速上传连接长期占用资源。

修改后务必先检查,再平滑加载:

sudo nginx -t
sudo systemctl reload nginx
curl -i -F "file=@upload-80m.bin" https://upload.example.com/api/files

nginx -t 失败时不得 reload。reload 会保留已有连接并由新 worker 使用新配置,通常比 restart 更适合线上变更。

大文件仍失败时的第二现场

如果 413 消失但出现 502、504 或上传中断,应继续检查请求体落盘和上游超时:

df -h /var/lib/nginx /tmp
sudo nginx -T | grep -n -E 'client_body_temp_path|proxy_(connect|send|read)_timeout|proxy_request_buffering'
sudo journalctl -u nginx --since '15 minutes ago'

默认情况下,超出 client_body_buffer_size 的请求体可能写入临时目录。临时盘空间不足、inode 耗尽或权限错误会造成新的上传失败。若使用对象存储,优先考虑让客户端通过短期签名 URL 直传;这样应用和 Nginx 不必长时间转发大流量,但签名必须限制对象前缀、Content-Type、大小和过期时间。

预防措施

  • 为每个上传接口定义产品级上限,并在前端、Nginx 和应用三层保持一致;
  • 将上传请求单独记录状态码、请求体大小(仅记录数值)、耗时和 request_id,便于区分 413、超时与上游失败;
  • 对上传接口设置鉴权、速率限制、并发限制和文件类型校验,文件先存隔离区再异步病毒扫描;
  • 监控 Nginx 临时目录的空间与 inode,并对 4xx 中的 413 数量设置告警;
  • 配置发布前用目标大小的测试文件走一次完整链路,覆盖 CDN、Ingress、Nginx 和应用。

总结

413 的关键不在于“把限制调大”,而在于确定拦截层、定位到具体上传路径,并按明确的业务上限安全放行。通过响应头、Nginx 错误日志和展开后的生效配置,可以迅速判断问题是否发生在应用之前;随后用范围最小的 location 配置、临时目录容量检查和上传接口防护措施,既恢复业务,也守住代理资源边界。