☰
Gitea LFS上传报413错误?Nginx请求体大小限制排查与配置详解
2026/10/18 14:00:27 网站建设 项目流程

如果你部署了Gitea并且给团队开了LFS,应该迟早会撞见这么一行报错:error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413。我第一次遇到时还挺纳闷:Gitea服务端没有任何错误日志,仓库里推个小文件没问题,一传几百MB的包就被打回,一开始还以为是LFS配额或者存储空间的问题。后来翻了半天配置才明白,413根本不是Gitea自己拦的,而是服务器前面那层Nginx在做限制。

这类问题不亲手排一次,很难有直观感受。网上一搜“gitea lfs 413”基本都能搜到一堆帖子,但多数只告诉你“调大client_max_body_size就行了”,没讲清楚这个参数到底该写在哪层、为什么默认值那么小、以及还有哪些看不见的坑。这篇文章就把我实际排查和修复的过程完整写下来,从原理到配置再到验证,尽量让你看完就能照着改,不用再踩一遍我踩过的坑。

1. HTTP 413的完整链路:先搞清楚是谁在拦你

1.1 从一次Gitea大文件推送失败说起

我的环境算比较典型:Gitea跑在一台内网服务器上,默认监听3000端口,前端用Nginx做HTTPS反向代理,仓库通过https://git.example.com/某团队/某仓库.git访问。刚开始整体都很顺畅,直到有一天同事要往仓库里推一个600MB左右的虚拟机镜像压缩包。

Git LFS的用量不大,所以我是按默认配置装的。那天客户端推了半天,最后冒出这么一句:

Uploading LFS objects: 100% (1/5, 1/5 done) error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 error: failed to push some refs to 'https://git.example.com/某团队/某仓库.git'

LFS对象上传的是100%,说明对象传输阶段已经开始了,真正失败的是LFS对象数据写入服务器的那一步。熟悉Git LFS流程的朋友应该知道,客户端在git push时会先发一个objects/batch请求,告诉服务器“我有哪些对象要传”,然后逐个把对象内容通过PUT请求传到服务器。单个PUT的请求体大小就约等于单个LFS对象的大小,600MB的镜像,对应的就是一个600MB大小的PUT请求。

所以问题很明确:某个环节收到了一个体积超大的HTTP请求,然后直接拒绝了。

1.2 从架构图上找出所有可能的拦截点

Gitea最常见的部署形态是:客户端 -> 反向代理(Nginx/Caddy等) -> Gitea应用(监听3000/3001等端口)。

这个链路里,能返回413的层其实有好几个:

  • 反向代理:以Nginx为例,默认client_max_body_size只有1m。只要请求体超过1MB,Nginx会在读取请求体阶段直接返回413,根本不会把请求转发给后端的Gitea。
  • Gitea应用本身:Go语言编写的HTTP服务默认不会限制请求体大小,但Gitea的LFS模块内部有LFS_MAX_FILE_SIZE配置,用来限制单个LFS对象的最大体积,超过也会拒绝。
  • 其他中间层:如果你还挂了云平台负载均衡、防火墙、WAF一类的产品,它们也可能有请求体限制,返回值同样可能是413。

但绝大多数情况下,坑都出在第一层,也就是Nginx的默认限制。记住一个关键点:Gitea自己默认情况下不会因为“请求体大”就返回413,但Nginx默认会。这是理解整篇文章的基础。

2. 快速定位:用错误页特征和日志确认拦截层

2.1 看一眼413页面的样式就能判断一半

413返回的响应内容是有“指纹”的。Nginx返回413时,会丢给你一个很朴素的白底HTML页面,标题是414 Request-URI Too Large的兄弟页面——也就是类似“413 Request Entity Too Large”这样的字样,页面底部还会出现一行“nginx”标识。

而Gitea应用返回的错误是结构化文本或JSON,和Nginx那个朴素页面完全不一样。如果你收到的413响应里能看到“nginx”字样,那基本可以断定是Nginx干的。这就是最快的判断手段。

当然,很多Git客户端不会把响应体打出来,你看到的只有那句HTTP 413 curl 22。这时候就要去日志里确认。

2.2 用Nginx日志确认是不是它拦截的

Nginx的访问日志和错误日志都会留下痕迹。访问日志里可以看到带有状态码413的请求:

grep " 413 " /var/log/nginx/access.log

错误日志里会有更明确的信息:

grep -i "too large" /var/log/nginx/error.log

常见的报错长这样:

client intended to send too large body: 629145600 bytes

看到client intended to send too large body这行字,说明Nginx已经在读取请求体阶段把请求拒绝了,请求压根没到Gitea。

反过来,如果Gitea的日志里出现了和LFS相关的错误,那才是Gitea层的问题。Gitea的日志文件默认在安装目录的log文件夹下,Docker部署则在/data/gitea/log下,文件名一般是gitea.log。用排除法,一对比基本就能锁定方向。

2.3 最省事的二分法:临时绕开Nginx试一次

如果还想更快,可以临时绕过Nginx,直接把Git客户端指向Gitea原端口。比如Gitea监听在3000端口,那我临时把仓库remote改成http://服务器IP:3000/某团队/某仓库.git,然后重新推送一次大文件。

如果这次推送能成功,那就100%说明是Nginx的问题,Gitea这边没毛病。如果直接访问原端口也失败,那才需要去怀疑Gitea自身的LFS限制配置。

这个“二分法”是我最推荐的做法,因为日志可能因为日志轮转、容器日志目录配置等原因没记录全,但实际请求行为骗不了人。

3. Nginx配置修正:从全局限制到精细化放行

3.1 client_max_body_size的正确位置与单位

确认问题在Nginx之后,最直接的修复方式就是把client_max_body_size调大。这个指令可以写在三个层级:

  • http块:全局生效,所有server都继承
  • server块:只对当前虚拟主机生效
  • location块:只对匹配到的路径生效

指令的值可以写1m、500m、2g这类带单位的写法,也可以写0表示完全不限制。

网上很多教程会直接在server块里写一个很大的值,比如:

server { listen 443 ssl; server_name git.example.com; client_max_body_size 2048m; location / { proxy_pass http://127.0.0.1:3000; 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是一个全局性的请求体限制,一旦写大,Web服务器上的所有接口都跟着变大,包括那些根本不需要大体积请求体的接口。从安全角度来说,这等于把整个服务器的上传口径放开了一圈。更好的做法是“精细放行”:普通Git操作保持一个合理阈值,只有LFS对象上传路径才放开。

3.2 只对LFS对象上传路径放开限制

Gitea的LFS接口路径遵循Git LFS的标准格式,通常长这样:

/{owner}/{repo}.git/info/lfs/objects/batch /{owner}/{repo}.git/info/lfs/objects/{oid}

其中objects/batch是批量查询请求,请求体很小;真正要放开的是objects/{oid}这个PUT上传请求。在Nginx里可以写一个正则location,专门匹配LFS对象上传路径:

server { listen 443 ssl; server_name git.example.com; # 普通路径,保留较小限制 location / { client_max_body_size 10m; proxy_pass http://127.0.0.1:3000; 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; } # 仅对 LFS 对象上传路径放开到 2GB location ~ ^/[^/]+/[^/]+\.git/info/lfs/objects/ { client_max_body_size 2048m; client_body_timeout 600s; proxy_pass http://127.0.0.1:3000; 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; proxy_request_buffering off; } }

这里面有几个值得展开说的细节。

client_max_body_size 2048m是根据你的业务来的,不是随便拍脑袋。如果团队最大文件是1GB,设成2GB就够,留一倍余量。没必要设成0把限制完全关掉,因为LFS对象体积理论上应该受你给仓库定的规范约束,设一个足够大的上限,既不会挡正常使用,又能防住异常行为。

client_body_timeout 600s是读请求体的超时时间。Nginx默认值是60秒,大文件上传时如果带宽不够,很容易在传输途中超时。超时返回的错误不是413,而是504或者connection reset一类的问题,但既然都在调Nginx的参数了,顺手把这个也改了能少踩一次坑。

proxy_request_buffering off是很多人忽略的一个点。Nginx默认会把整个客户端请求体缓冲到磁盘,再转发给后端。大文件上传时,这意味着Nginx要先在磁盘上写一份完整副本,再把数据转发给Gitea。磁盘慢的话整个上传会变得很拖沓。关闭之后,Nginx会边收边转发,大文件上传的体感会好很多,Gitea自己也支持流式接收。

3.3 改完配置文件后必须做的验证操作

改完Nginx配置,先测试语法再重载:

nginx -t nginx -s reload

nginx -t会告诉你配置文件有没有语法错误。有报错就乖乖回头检查和引号、分号、花括号,不要带着错误重载。nginx -s reload是平滑重载,已经建立的连接不会断,所以白天操作也不会影响在线用户。

如果用的是容器版的Nginx,重载方式稍微有点区别:

docker exec nginx nginx -t docker exec nginx nginx -s reload

4. Gitea服务端LFS参数:反向代理之外的第二道闸

4.1 别忽略app.ini里的LFS_MAX_FILE_SIZE

调完Nginx后,大部分场景已经能解决413了。但如果你绕过Nginx直接访问Gitea原端口,或者反代已经不拦你了,仍然遇到413或类似的上传失败,那就得看Gitea这边的LFS配置。

Gitea的配置文件是app.ini。Linux下用二进制方式安装的话,通常在custom/conf/app.ini;Docker部署的话,一般在/data/gitea/conf/app.ini。在里面找到[server]段,Gitea对LFS限制相关的配置项长这样:

[server] ; 是否启用 LFS 服务 LFS_START_SERVER = true ; 单个 LFS 对象的最大体积,单位 MB,0 表示不限制 LFS_MAX_FILE_SIZE = 2048 ; 单次 batch 请求允许处理的对象数量,0 表示不限制 LFS_MAX_FILES_PER_BATCH = 0

重点是LFS_MAX_FILE_SIZE。这个参数的单位是MB,默认值是0,也就是不限制。注意,Gitea文档里很多示例会写成LFS_MAX_FILE_SIZE = 1024,那是示例,不是默认值,别搞混了。

如果你的部署环境里显式给这个字段填过一个较小的值,比如100,那超过100MB的LFS对象会被Gitea直接拒绝,返回的错误也可能是413或者别的HTTP错误。这种情况下,即使Nginx完全放行了,上传依然失败。

4.2 修改app.ini后如何让配置生效

修改app.ini后,需要重启Gitea服务才能生效:

  • 二进制方式:重启Gitea的systemd服务,比如systemctl restart gitea,或者重新执行你原来启动Gitea的进程管理命令。
  • Docker Compose方式:docker compose restart gitea

一个容易被忽略的坑是:Gitea官方Docker镜像支持通过环境变量覆盖配置文件项,而且环境变量的优先级高于配置文件。也就是说,如果你在docker-compose.yml里写过类似这样的配置:

services: gitea: image: gitea/gitea:latest environment: - GITEA__server__LFS_MAX_FILE_SIZE=100

那么即使你把容器里的app.ini改成2048,重启后Gitea依然会以环境变量里的100为准。这种“改了配置但没生效”的情况我见过好几次。排查思路很简单:进容器看一眼实际生效的配置值:

docker compose exec gitea grep -n "LFS_MAX_FILE_SIZE" /data/gitea/conf/app.ini

如果文件里是一回事,实际行为是另一回事,那就去docker-compose.yml里找有没有对应的GITEA__*环境变量。

4.3 理解Git LFS客户端为什么不会“一次传一个仓库”

还有一个值得顺带搞清楚的点:Git LFS客户端在上传时,并不是把整个仓库的所有LFS对象打包成一个请求。它的流程是先发一个objects/batch请求,告诉服务器“我要传这5个对象”,服务器返回允许后,客户端再逐个PUT对象数据。所以单个HTTP请求的大小,约等于单个LFS对象的体积,而不是仓库里所有大文件加起来的总体积。

这就是为什么Nginx的client_max_body_size只需要比“单个文件上限”大一点就行,不需要大到“整个仓库体积加总”那种级别。很多人误以为要设得很大,其实是对LFS的传输模型不了解。

5. Docker部署和中间层:容器环境比你想的更容易踩坑

5.1 容器里改配置不生效,多半是卷和权限问题

Docker部署Gitea时,配置文件通过挂载卷持久化。官方镜像的数据目录是/data,里面包含了gitea/conf/app.ini、gitea/log以及LFS的存储目录。

遇到“我在宿主机改了配置文件,但重启容器后不生效”的情况,先按这个顺序排查:

  1. 确认挂载关系:检查docker-compose.yml里是不是真的把宿主机目录挂载到了/data,还是把容器目录挂载到了奇怪的位置。
  2. 确认文件路径:Gitea找配置文件的位置是/data/gitea/conf/app.ini,不少新手会把宿主机上任意一个.ini文件挂进去,名字对但目录不对,Gitea根本不会读。
  3. 确认文件权限:Gitea容器内运行用户是git(UID通常为1000),如果宿主机上的app.ini权限是root:root且没有其他用户读权限,容器内可能读不到或写不了,导致行为诡异。

进容器确认一下实际路径和内容,是最稳的方法:

docker compose exec gitea more /data/gitea/conf/app.ini

5.2 如果Nginx也是容器:改的是哪个Nginx?

很多人会犯一个低级错误:Gitea启动在容器里,Nginx跑在宿主机上;也可能反过来,Gitea和Nginx都是容器,但分属不同的Compose项目。这时候改Nginx配置要改对地方——改的是真正承担反向代理职责的那台Nginx,不是随便哪个。

判断“客户端请求到底打到哪个服务”的最简单方法,是看Nginx日志里的IP来源。如果客户端连的域名指向的是宿主机Nginx,那肯定要改宿主机的Nginx配置文件;如果是容器编排里单独起的nginx服务,那要进对应容器改。

另外,云环境里还有一种情况:域名解析到负载均衡服务,再由负载均衡转发到宿主机80/443端口。这时候413可能根本不在Nginx层面发生,而是被负载均衡产品拦下的。这类产品通常有自己的请求体大小上限,部分产品默认甚至只有几十MB,而且不同控制台的配置入口五花八门。遇到这种情况,先翻一下负载均衡的访问日志,看看413是不是它产生的。

5.3 Caddy等其他反向代理的天然差异

如果你用的是Caddy而不是Nginx,那么默认情况下它不会限制请求体大小,因为Caddy没有内置一个类似client_max_body_size的全局默认阈值。这意味着Gitea + LFS + Caddy的组合通常不会出现413,除非你在Caddyfile里主动写了request_body { max_size ... }这类指令。

这不是让你换Caddy,而是帮你理解不同代理层的默认行为差异,排查时不会死磕一个方向。

6. 验收:从构造大文件到完整推送的实测记录

6.1 构造一个超过旧阈值的大文件做回归测试

配置改完之后不能只看配置,要真正推一把大文件验证。我的做法是分三步。

第一步,找一个临时工作目录,生成一个大小超出旧阈值的测试文件。假设之前阈值是1MB,那生成500MB就够有代表性了:

dd if=/dev/zero of=test-500m.bin bs=1M count=500

第二步,在一个已有的Git仓库里启用LFS并跟踪该类型文件:

git lfs install --local git lfs track "*.bin"

git lfs track会自动创建或更新.gitattributes文件。注意要把.gitattributes一起提交,否则其他人clone下来后不会知道*.bin文件要走LFS。

第三步,提交并推送:

git add .gitattributes test-500m.bin git commit -m "test lfs push" git push origin main

如果一切正常,推送输出里能看到LFS对象的上传进度,最终是successful或者类似提示。如果仍然有问题,那么Nginx错误日志、Gitea日志都会留下线索,对照排查即可。

6.2 常见报错对照表:一个表帮你快速定位

每次排错都从几个特征里快速对号入座,效率最高。我自己常用的判断表如下:

现场表现问题层级切入点
413响应页面上带“nginx”字样Nginx反向代理调client_max_body_size
Nginx错误日志有too large bodyNginx反向代理同上
Gitea日志没有相关错误,请求没到GiteaNginx反向代理同上
Gitea日志出现LFS大小相关错误Gitea应用层调LFS_MAX_FILE_SIZE
绕过Nginx直连Gitea端口后上传成功Nginx反向代理修Nginx配置
绕过Nginx直连仍然失败Gitea应用层修app.ini或环境变量

6.3 一个常见误区:http.postBuffer对413无效

搜LFS推送问题的时候,经常会看到有人建议调大http.postBuffer,例如:

git config http.postBuffer 524288000

这个参数解决的是老的Git HTTP传输中“请求体太大被HTTP 411 Length Required或HTTP 500”这类问题,主要针对普通Git对象的大单包推送。对于413这种“服务器方明确拒绝过大请求体”的情况,http.postBuffer改了也没有用处,因为问题不在客户端,而在服务器端对请求体大小的限制。识别出这个误区,能帮你少走弯路。

6.4 大文件推送完成之后,还建议顺手做的两件事

413的问题解决后,有两件小事我建议顺手做掉,避免后续还有别的隐患。

一是给Nginx单独记录一份LFS相关的访问日志。比如在精准location里加一行:

access_log /var/log/nginx/lfs-access.log;

这样一来,以后LFS上传再出问题,直接看这份日志,别去几百MB的通用访问日志里大海捞针。

二是把Gitea的LFS内容目录放到独立数据盘上。LFS文件通常体积大且不会被频繁改写,适合放机械盘或大容量SSD。这个在Gitea配置里对应的是[server]段的LFS_CONTENT_PATH,比如:

[server] LFS_CONTENT_PATH = /data/lfs

如果你的/data目录和系统盘挤在一起,LFS一满,整个服务都可能遭殃,提前规划好能省很多事。

最后再说说我的个人体会。413这个报错之所以坑,是因为它面前挡着一层透明的默认值——Nginx的client_max_body_size默认1MB,这是个非常隐蔽的“服务端接受度”参数,而绝大多数用户根本没意识到它存在。我在后来所有Gitea加反代的配置模板里,都会默认把LFS对象上传路径的client_max_body_size单独写出来,再配一份单独的LFS访问日志。这样一来,每上一个新环境,都不用再经历一次“白天收到报错、晚上翻日志定位”的过程了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询