一次Nginx反向代理配置错误导致上传文件大小限制失效的排查实录

问题背景

某周一早上刚到公司,业务部门的同事就找过来反映,说他们的内部文档管理系统在上传大于 10MB 的文件时,总是弹出错误提示。之前这个系统运行一直正常,但上周刚完成了一次迁移,把原来跑在 Tomcat 上的应用挪到了新的 Docker 容器里,并在前端新增了一台 Nginx 作为反向代理。迁移完成后做了基本的冒烟测试,上传小文件没问题,但大文件的情况没有测试到,结果今天业务侧才发现问题。

影响范围较大,整个文档管理系统的使用人数大约 60 人,每天有大量合同、图纸、报告需要上传,部分文件超过 10MB,属于正常业务场景。


故障现象

用户在浏览器端上传文件时,当文件大小超过约 8MB 时,页面返回如下错误:

1
413 Request Entity Too Large

浏览器 Network 面板可以看到请求直接被 Nginx 拒绝,HTTP 状态码 413,没有到达后端应用。

开发同事说他已经在应用的 application.properties 里把 Spring Boot 的上传限制改成了 100MB:

1
2
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB

容器重启后依然报 413,于是找到运维这边来排查。


排查过程

第一步:确认报错来源

先看 Nginx 的 error log:

1
tail -f /var/log/nginx/error.log

日志输出:

1
2022/03/15 09:12:43 [error] 1234#1234: *567 client intended to send too large body: 9437184 bytes, client: 192.168.10.88, server: docs.internal.company.com, request: "POST /api/upload HTTP/1.1", host: "docs.internal.company.com"

很明确:是 Nginx 报的错,不是后端应用。说明请求根本没到 Java 应用这层就被 Nginx 拒掉了。

第二步:检查 Nginx 配置

查看当前生效的 Nginx 配置:

1
nginx -T 2>/dev/null | grep -i "client_max_body_size"

输出:

1
client_max_body_size 8m;

找到了!但配置文件里明明应该改过的……继续追查配置文件层级:

1
find /etc/nginx -name "*.conf" | xargs grep -l "client_max_body_size"

发现了三处:

  1. /etc/nginx/nginx.conf(http 块):client_max_body_size 100m;
  2. /etc/nginx/conf.d/default.conf(server 块):client_max_body_size 8m;
  3. /etc/nginx/conf.d/docs.conf(location 块):未配置

第三步:理解 Nginx 配置继承规则

这里就需要了解 Nginx 配置的继承机制了。client_max_body_size 可以出现在三个层级:

  • http {}
  • server {}
  • location {}

优先级规则是:子块的配置会覆盖父块。 也就是说,server {} 里配置了 8m,就会覆盖掉 http {} 里的 100m,即使 http {} 里的值更大。

追问负责这次迁移的同事,他说迁移之前的 default.conf 是从旧机器上直接拷过来的,旧环境里这个文档系统并不走那台 Nginx,所以 default.conf 里的 8m 限制之前没有影响,但迁移后新 Nginx 又用了同一个 default.conf,就埋下了这个坑。

第四步:验证其他 server 配置

既然 default.conf 里有残留的 8m 限制,那需要确认这个配置是否影响到了 docs.conf 里的 server 块。

查看 docs.conf

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name docs.internal.company.com;

location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

这个 server {} 块里没有配置 client_max_body_size,按理应该继承 http {}100m。但实际上 default.conf 里的那个 server {} 块只影响它自己,不会影响其他 server {} 块。

那到底是哪里的 8m 在生效?

第五步:再次仔细检查继承关系

重新 nginx -T 全量输出,这次逐段仔细看:

1
nginx -T 2>/dev/null

在仔细阅读输出后,发现 docs.conf 里其实还有一段被注释掉但没有完全注释干净的配置:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name docs.internal.company.com;
client_max_body_size 8m; # 这行没被注释掉!

# client_max_body_size 100m; # 尝试修改,但改错位置了

location / {
proxy_pass http://127.0.0.1:8080;
...
}
}

原来,那位同事确实改过,但他把新的值写成了注释,同时没把旧的 8m 那行删掉,导致 8m 依然生效。


解决方案

修改 docs.conf,移除多余的限制配置,直接在 server {} 块里设置正确的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
server_name docs.internal.company.com;
client_max_body_size 200m; # 设置为 200m,留足余量

location / {
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_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
}

验证配置语法无误:

1
nginx -t

输出:

1
2
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

热重载:

1
nginx -s reload

测试上传 50MB 文件,成功。用户侧验证通过,问题解决。


根因分析

根本原因是配置迁移时遗留了旧配置,且修改时操作不规范(改了注释而没删除旧配置行)。client_max_body_size 8m; 这行代码实际上一直生效,在旧环境中没有问题是因为旧环境的这台 Nginx 不承载文档系统的流量,迁移后该配置直接生效并产生影响。

此外,该同事误认为在 http {} 块设置了 100m 后,server {} 块里的 8m 会被覆盖,对 Nginx 配置的优先级规则理解有误。


预防措施

1. 配置迁移规范化

迁移配置前应先整理清楚每项配置的作用,不应直接拷贝旧配置,特别是对限制类配置(大小、超时、速率)要逐一核对。

2. 搭建迁移验证 Checklist

关键迁移场景(如文件上传、超时、鉴权等)必须列入上线前测试清单,不能只做功能冒烟测试。

3. 定期 nginx -T 全量审计

可通过脚本定期输出 nginx -T 并做配置 diff,发现异常配置:

1
nginx -T 2>/dev/null | grep -E "(client_max_body_size|proxy_read_timeout|proxy_send_timeout)" > /tmp/nginx_limit_audit.txt

4. 统一配置管理

推荐将 Nginx 配置纳入 Git 版本管理,所有修改通过 MR/PR 审核,避免直接改配置文件后忘记清理。


总结

这次故障表面上是个很简单的 413 问题,但排查过程中踩了两个坑:一是对 Nginx 配置的层级覆盖规则不熟悉,二是配置文件里存在”半改”的残留配置,干扰了判断。

教训:迁移时不要直接照搬旧配置,要理解每一行配置的含义;修改配置时要彻底,不要留注释残骸;上线前的测试场景要覆盖到实际业务的边界情况(比如大文件上传、长时间操作等)。