适用版本:Gitea 1.20 / 1.21 / 1.22(配置文件键名以官方文档为准)
目标读者:负责 Gitea 部署与运维的工程师
关键词:Gitea、CDN、对象存储、MinIO、SERVE_DIRECT、STATIC_URL_PREFIX
目录
1. 引言:为什么改造
1.1 非 CDN 模式的流量痛点
在默认(非 CDN)部署下,Gitea 的所有响应都由源站(Gitea + 其反向代理)直接处理。实际运行中会碰到以下问题:
- 静态资源直连源站:前端 JS/CSS/字体、头像、附件、仓库归档(zip/tar.gz)等全部从源站拉取,源站带宽被大量非核心流量占用。
- 带宽打满:大量用户
git clone 会触发归档打包与下载,单次克隆动辄几十上百 MB,高峰期直接打满源站出口带宽。
- 跨地域访问慢:源站通常部署在单一地域,远端用户访问延迟高、丢包重传严重。
- 抗压能力弱:热点仓库被反复克隆时,源站 CPU(打包压缩)与带宽同时承压,容易雪崩。
- 难以水平扩展:源站垂直扩容成本高,扩副本又会带来存储一致性问题。
1.2 CDN 化收益
- 卸载源站流量:静态资源与归档由 CDN 边缘节点命中返回,源站只承担少量回源请求。
- 加速:边缘节点就近接入,跨地域延迟显著下降。
- 抗压:CDN 天然具备高并发与 DDoS 缓冲能力,保护源站。
- 可扩展:源站与分发解耦,可独立扩展对象存储与 CDN。
核心结论:CDN 化的本质是"把大文件与静态资源从源站剥离出去",让 Gitea 专注处理元数据与业务逻辑。
2. 原理:Gitea 与静态资源的关系
2.1 静态资源类型
| 资源类型 | 说明 | 是否适合 CDN |
| 公共前端资源(JS/CSS/字体/图片) | /assets/ 下的构建产物 | ✅ 适合,可长时间缓存 |
| 用户头像 | 本地头像或 Gravatar 代理 | ✅ 适合 |
| 附件(Issue/PR 上传、Release 附件) | /attachments/ 下的对象 | ⚠️ 私有仓库附件需鉴权 |
| 仓库归档(zip/tar.gz) | clone/download 时动态或预生成 | ⚠️ 私有仓库归档需鉴权 |
| LFS 对象 | 大文件存储 | ⚠️ 私有仓库需签名直链 |
2.2 Gitea 不内置 CDN
Gitea 自身不是 CDN,也不内置 CDN 能力。它通过两条路径实现 CDN 化:
- 静态资源前缀指向 CDN:把
/assets/、头像等静态资源的 URL 前缀改写为 CDN 域名,由 CDN 直接返回。
- 对象存储承载大文件:把附件、LFS、归档等落入 MinIO/S3/OSS,并让 Gitea 通过重定向到带签名的临时直链把流量交给对象存储与 CDN。
两者可独立或组合使用。
3. 关键配置项详解(app.ini)
3.1 改造前 / 后对照表
| 配置项 | 改造前(非 CDN) | 改造后(CDN 模式) | 说明 |
[server] ROOT_URL | https://git.example.com/ | https://git.example.com/(不变) | 必须保持为 Gitea 自身公开地址 |
[server] STATIC_URL_PREFIX | 空 | https://cdn.example.com | 静态资源前缀指向 CDN |
[server] OFFLINE_MODE | false | false(或按需 true) | 离线模式,见下 |
[storage] STORAGE_TYPE | local | minio / s3 | 存储后端 |
[storage] SERVE_DIRECT | false | true | 重定向到对象存储签名直链 |
[picture] GRAVATAR_SOURCE | gravatar | 自建/镜像源或disable | 头像 CDN 化 |
[picture] DISABLE_GRAVATAR | false | 按需true | 禁用外部 Gravatar |
3.2 [server] ROOT_URL ——【重要】
ROOT_URL 必须保持为 Gitea 实例自身的公开地址(如 https://git.example.com/)。
⚠️ 绝不能把 ROOT_URL 指向 CDN 域名。
原因:ROOT_URL 决定:
- 路由与页面生成的绝对链接;
- OAuth/Webhook 的回调地址;
git clone 时输出的克隆地址。
⚠️ OAuth 回调地址必须使用 HTTPS,且必须指向 ROOT_URL(Gitea 自身),绝不能指向 CDN,否则存在 OAuth 劫持风险。
- OAuth 客户端配置时,回调地址必须与
ROOT_URL 一致。
- 一旦指向 CDN,会造成登录回调失败、克隆地址错误、页面跳转异常等严重问题。
3.3 [server] STATIC_URL_PREFIX
将静态资源指到 CDN 域名,例如 https://cdn.example.com。
旧版配置项 ASSETS_URL_PREFIX 已被 STATIC_URL_PREFIX 取代,新部署请使用后者。
3.4 [server] OFFLINE_MODE
false:允许加载外部资源(如外部 Gravatar、CDN 字体)。
true:禁止访问外部网络资源,适合内网隔离环境。
CDN 化后若 CDN 属自建可控,按需保留 false。
3.5 [storage] STORAGE_TYPE
取值:local(默认)、minio、s3。
改为对象存储后,附件、LFS、归档等大文件不再占用本地磁盘,并且可以被 CDN 直接回源。
3.6 [storage] SERVE_DIRECT ——【关键】
SERVE_DIRECT = true 时,Gitea 对附件、LFS、仓库归档不再自己转发字节流,而是重定向(HTTP 302)到对象存储生成的带签名临时直链。
这是真正卸载源站流量的关键开关:用户的下载流量直接从对象存储/CDN 走,源站只返回一个 302。
3.7 [storage.minio] 真实键名
| 键名 | 说明 |
MINIO_ENDPOINT | 对象存储访问地址,如minio.example.com:9000 |
MINIO_ACCESS_KEY_ID | Access Key |
MINIO_SECRET_ACCESS_KEY | Secret Key |
MINIO_BUCKET | 存储桶名称,如gitea |
MINIO_USE_SSL | 是否使用 SSL,true / false |
MINIO_BASE_PATH | 桶内基础路径前缀 |
MINIO_LOCATION | 区域(region),如us-east-1 |
MINIO_CHECKSUM_ALGORITHM | 校验算法,如md5 / sha256 |
3.8 [picture] 头像 CDN 化
GRAVATAR_SOURCE:头像来源,可设为自建镜像或 disable。
DISABLE_GRAVATAR:true 时禁用外部 Gravatar,避免请求外部服务带来的跨境延迟。
4. 改造方案 A:纯 CDN 回源
思路:保留源站与本地存储,Nginx 按路径分离静态资源,CDN 回源 Gitea/Nginx。
Nginx 路径分离示例:
server {
listen 443 ssl;
server_name git.example.com;
# 公共前端资源:长缓存
location /assets/ {
proxy_pass http://127.0.0.1:3000;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 头像:中等缓存
location /avatars/ {
proxy_pass http://127.0.0.1:3000;
expires 1d;
add_header Cache-Control "public";
}
# 安全响应头
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
# 附件:需鉴权,禁止公共缓存
location /attachments/ {
proxy_pass http://127.0.0.1:3000;
add_header Cache-Control "private, no-store";
}
location / {
proxy_pass http://127.0.0.1:3000;
}
}
CDN 侧:把 /assets/、/avatars/ 配置为缓存回源,/attachments/、归档路径设为不缓存或透传。
适用场景:不想引入对象存储、只想先卸载静态资源与加速前端的小型部署。
局限:大文件(归档/LFS)仍从源站出口,带宽痛点未根本解决。
5. 改造方案 B(推荐):对象存储 + CDN
思路:以 MinIO/S3/OSS 作为 STORAGE_TYPE,开启 SERVE_DIRECT = true,CDN 回源对象存储。
[storage]
STORAGE_TYPE = minio
SERVE_DIRECT = true
[storage.minio]
MINIO_ENDPOINT = minio.example.com:9443
MINIO_ACCESS_KEY_ID = your-access-key
MINIO_SECRET_ACCESS_KEY = your-secret-key
MINIO_BUCKET = gitea
MINIO_USE_SSL = true
MINIO_BASE_PATH = gitea/
MINIO_LOCATION = us-east-1
MINIO_CHECKSUM_ALGORITHM = md5
⚠️ 示例仅用于演示,生产环境使用占位符并通过环境变量或密钥管理服务提供真实凭证。MINIO_ACCESS_KEY_ID 和 MINIO_SECRET_ACCESS_KEY 严禁硬编码在 app.ini 中。
TLS 版本要求:MinIO / CDN / Nginx 全链路应至少启用 TLSv1.2(禁用 TLSv1.0/TLSv1.1),端口使用 9443(HTTPS)。
生产环境密钥管理建议:
- 使用 systemd 的
EnvironmentFile 或启动脚本注入环境变量
- 或使用 HashiCorp Vault、AWS Secrets Manager 等密钥管理服务
- Gitea
app.ini 不支持 ${VAR} 插值语法
MinIO Bucket 安全策略:
- Bucket 必须设置为私有,禁止匿名 GET/PUT
- Access Key 仅授予必要的权限(GetObject/PutObject/ListenBucketNotification)
- 定期轮换密钥,限制 IP 白名单
数据流:用户请求归档 → Gitea 返回 302 到对象存储签名直链 → 用户流量走 CDN/对象存储,源站仅处理元数据。
两方案对比
| 维度 | 方案 A(纯 CDN 回源) | 方案 B(对象存储 + CDN) |
| 大文件流量 | 仍走源站 | 完全卸载 |
| 部署复杂度 | 低 | 中(需对象存储) |
| 扩展性 | 一般 | 高 |
| 安全边界 | 依赖 Nginx/CDN 规则 | 依赖签名直链 |
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
推荐方案 B:只有把大文件迁到对象存储并开启 SERVE_DIRECT,才能从根本上卸载源站带宽。
6. 分步操作
① 备份 app.ini 与数据
# 备份配置
cp /etc/gitea/app.ini /etc/gitea/app.ini.bak.$(date +%F)
# 备份 PostgreSQL
pg_dumpall -U postgres > /backup/gitea_pg_$(date +%F).sql
# 备份数据目录(含本地存储、LFS)
tar czf /backup/gitea_data_$(date +%F).tar.gz /var/lib/gitea
② 准备 CDN / 对象存储
- 申请 CDN 域名(如
cdn.example.com)并完成 ICP/证书配置。
- 配置回源:方案 A 回源
git.example.com;方案 B 回源对象存储。
- 配置缓存规则:
/assets/ 长缓存,/avatars/ 中缓存,附件/归档按鉴权策略。
③ 修改 app.ini
改造前:
[server]
ROOT_URL = https://git.example.com/
OFFLINE_MODE = false
[storage]
STORAGE_TYPE = local
SERVE_DIRECT = false
[picture]
GRAVATAR_SOURCE = gravatar
DISABLE_GRAVATAR = false
改造后:
[server]
ROOT_URL = https://git.example.com/
STATIC_URL_PREFIX = https://cdn.example.com
OFFLINE_MODE = false
[storage]
STORAGE_TYPE = minio
SERVE_DIRECT = true
[storage.minio]
MINIO_ENDPOINT = minio.example.com:9443
MINIO_ACCESS_KEY_ID = your-access-key
MINIO_SECRET_ACCESS_KEY = your-secret-key
MINIO_BUCKET = gitea
MINIO_USE_SSL = true
MINIO_BASE_PATH = gitea/
MINIO_LOCATION = us-east-1
MINIO_CHECKSUM_ALGORITHM = md5
> ⚠️ **示例仅用于演示,生产环境使用占位符并通过环境变量或密钥管理服务提供真实凭证**。`MINIO_ACCESS_KEY_ID` 和 `MINIO_SECRET_ACCESS_KEY` 严禁硬编码在 `app.ini` 中。
> **生产环境密钥管理建议**:
> - 使用 systemd 的 `EnvironmentFile` 或启动脚本注入环境变量
> - 或使用 HashiCorp Vault、AWS Secrets Manager 等密钥管理服务
> - Gitea `app.ini` 不支持 `${VAR}` 插值语法
[picture]
GRAVATAR_SOURCE = disable
DISABLE_GRAVATAR = true
若从 local 切换到对象存储,需先迁移历史数据:可使用 gitea dump 导出后再导入,或通过 mc mirror 将本地存储目录同步到桶内对应路径。
④ 配置反向代理 / 静态分发
- 方案 A:按第 4 章 Nginx 示例配置路径分离。
- 方案 B:确保 CDN 回源对象存储,并为桶设置适当缓存头。
⑤ 重启验证
sudo systemctl restart gitea
sudo systemctl status gitea
7. 验证方法
7.1 检查响应头
# 检查静态资源是否命中 CDN
curl -I https://cdn.example.com/assets/index.js
# 检查附件/归档是否 302 到对象存储
curl -I https://git.example.com/user/repo/archive/main.zip
观察响应头:
cf-cache-status: HIT(Cloudflare)或 x-cache: HIT(Nginx/常见 CDN)表示命中缓存。
Location: 指向对象存储直链(含签名)表示 SERVE_DIRECT 生效。
7.2 CDN 命中率
在 CDN 控制台查看 Cache Hit Ratio,稳态下静态资源应 > 90%。
7.3 源站流量对比
对比改造前后源站出口带宽与 QPS 曲线,归档/附件下载应显著下降。
7.4 浏览器 DevTools
打开 Network 面板,检查静态资源 Remote Address 是否指向 CDN、归档请求是否 302。
8. 常见问题
9. 回滚方案
改造失败时可快速回滚:
# 1. 停止服务
sudo systemctl stop gitea
# 2. 还原 app.ini
cp /etc/gitea/app.ini.bak.2026-09-26 /etc/gitea/app.ini
# 3. 如涉及对象存储数据,按需还原数据目录
# tar xzf /backup/gitea_data_2026-09-26.tar.gz -C /
# 4. 重启
sudo systemctl start gitea
回滚前建议再次备份当前配置,确保可双向切换。
10. 总结与参考资料
CDN 化改造的要点可归纳为三条:
ROOT_URL 指向 Gitea 自身,STATIC_URL_PREFIX 指向 CDN —— 分清"谁是站点"和"谁是分发"。
- 大文件一定要走对象存储,并开启
SERVE_DIRECT = true,用签名直链卸载源站流量。
- 私有资源绝不能公共缓存,安全边界依赖签名直链与 CDN 缓存规则。
遵循以上原则,配合备份与回滚预案,即可平滑完成从非 CDN 到 CDN 模式的迁移。
参考资料