Gitea 仓库从非 CDN 模式改造为 CDN 模式实战指南

适用版本: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 化:

  1. 静态资源前缀指向 CDN:把 /assets/、头像等静态资源的 URL 前缀改写为 CDN 域名,由 CDN 直接返回。
  2. 对象存储承载大文件:把附件、LFS、归档等落入 MinIO/S3/OSS,并让 Gitea 通过重定向到带签名的临时直链把流量交给对象存储与 CDN。

两者可独立或组合使用。


3. 关键配置项详解(app.ini)

3.1 改造前 / 后对照表

配置项改造前(非 CDN)改造后(CDN 模式)说明
[server] ROOT_URLhttps://git.example.com/https://git.example.com/(不变)必须保持为 Gitea 自身公开地址
[server] STATIC_URL_PREFIX空https://cdn.example.com静态资源前缀指向 CDN
[server] OFFLINE_MODEfalsefalse(或按需 true)离线模式,见下
[storage] STORAGE_TYPElocalminio / s3存储后端
[storage] SERVE_DIRECTfalsetrue重定向到对象存储签名直链
[picture] GRAVATAR_SOURCEgravatar自建/镜像源或disable头像 CDN 化
[picture] DISABLE_GRAVATARfalse按需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_IDAccess Key
MINIO_SECRET_ACCESS_KEYSecret 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. 常见问题

  • CDN 缓存不更新:前端发版后需在 CDN 控制台主动 purge(刷新)对应目录;/assets/ 建议用带哈希文件名的不可变资源,避免刷新。
  • ROOT_URL 配错导致 404/跳转异常:检查 ROOT_URL 是否被误指向 CDN,务必改回实例地址。
  • CORS 跨域:CDN 域名与站点域名不同源,需在 CDN 配置 Access-Control-Allow-Origin 等响应头。
    • ⚠️ Access-Control-Allow-Origin 必须设置为 Gitea 域名(如 https://git.example.com),不能使用 * 通配符
    • 跨域资源共享的安全边界:仅允许必要的 API 调用,禁止跨域脚本执行
  • 私有仓库归档与 LFS 的安全边界:绝不可对私有仓库的归档/LFS 做公共缓存。SERVE_DIRECT 通过带签名的临时直链访问对象存储,签名短时效,避免越权;CDN 侧对这类路径应设为不缓存或仅缓存鉴权后的响应。
  • 签名直链算法:Gitea 对象存储直链采用 HMAC-SHA256 签名,包含时间戳、过期时间(建议 5-15 分钟)、请求 ID(防重放)。签名计算方式为:
    signature = HMAC-SHA256(secret_key, timestamp + request_id + path)
    
  • 有效期与防重放:签名有效期为 5-15 分钟,服务端校验时间戳与 nonce(请求 ID),拒绝过期或重复请求。
  • HTTPS 混合内容:若站点为 HTTPS 而 CDN 为 HTTP,浏览器会拦截资源,必须为 CDN 配置有效证书。
    • 检测方法:浏览器 DevTools 的安全面板或 Lighthouse 审计
    • 风险说明:浏览器会阻止混合内容,导致页面资源加载失败
    • 解决方案:必须在 CDN 上配置有效证书(推荐 Let's Encrypt)

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 化改造的要点可归纳为三条:

  1. ROOT_URL 指向 Gitea 自身,STATIC_URL_PREFIX 指向 CDN —— 分清"谁是站点"和"谁是分发"。
  2. 大文件一定要走对象存储,并开启 SERVE_DIRECT = true,用签名直链卸载源站流量。
  3. 私有资源绝不能公共缓存,安全边界依赖签名直链与 CDN 缓存规则。

遵循以上原则,配合备份与回滚预案,即可平滑完成从非 CDN 到 CDN 模式的迁移。

参考资料

# gitea 


标 题:《Gitea 仓库从非 CDN 模式改造为 CDN 模式实战指南》
作 者:zeekling
提 示:转载请注明文章转载自个人博客:浪浪山旁那个村

评论

取消