ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

手把手教你用Cloudflare Workers搭建稳定快速的GitHub镜像站

手把手教你用Cloudflare Workers搭建稳定快速的GitHub镜像站 GitHub镜像站这话题我这两年反复折腾过踩了不少坑也总结出一套比较顺手的做法。身边总有同事和朋友问我GitHub怎么又打不开了、clone半天没反应、下载release文件一直失败。与其一次次去搜那些时好时坏的公共镜像不如自己动手搭一个专属的GitHub镜像站按需配置、控制缓存、限流防刷用起来既稳定又舒服。这篇就把从原理到落地的完整过程写下来经历过的人一看就懂刚接触的照着做也能跑通。1. 拆需求GitHub镜像站到底要解决什么先别急着写代码得把需求想清楚。很多人搭镜像站只是一时兴起结果搭完发现网页能开、图片挂了、clone还是慢最后不了了之。根本原因就是没搞明白GitHub镜像站要覆盖哪些访问场景。1.1 镜像站和普通网站代理的本质区别普通网站代理比如公司内网访问外部系统通常只要求“能打开页面”“能提交表单”数据量小、交互简单。GitHub完全不是这么回事。它表面上是一个代码托管网站背后却牵扯到至少四类完全不同的流量网页请求浏览仓库主页、文件列表、issue、PR这类是标准HTML页面和普通网站没区别。静态资源头像、图标、CSS、JS散落在*.githubusercontent.com等域名上单独访问经常抽风。代码下载git clone、git fetch、git push走的是Git智能HTTP协议请求头、响应体都和普通网页不一样还有大量重定向逻辑。大文件下载release产物、源码压缩包archive体积从几MB到几个GB都有。一个真正的镜像站四个场景都要处理。只代理github.com这一条域名顶多让你能打开仓库主页其余功能全废。1.2 一个可用的GitHub镜像要覆盖哪些路径拆开来看核心路径其实就那么几条访问目标原始域名说明仓库网页github.com主页、issue、PR等用户头像/图标avatars.githubusercontent.com页面里的静态图片文件原始内容raw.githubusercontent.com直接访问单文件代码仓库下载codeload.github.comzip/tar.gz下载走这里大文件发布物objects.githubusercontent.comrelease附件实际存储地址Git协议交互github.comclone/fetch/push都走主站看清楚这张表就明白了一个关键点镜像站的麻烦不在于“代理一个域名”而在于“把所有相关域名统一映射到自己的入口”并且替换页面里所有硬编码的原始链接让用户访问时始终留在镜像域内、不跳回原站。这也是后面URL改写和反向代理配置的核心目标。2. 原理先行反向代理、URL改写与缓存不把底层原理讲透后面的配置就是瞎调。GitHub镜像站本质上是一个带“内容改写”的反向代理三个关键技术点绕不开。2.1 反向代理是镜像站的骨架反向代理简单说就是用户请求先落到你的服务器/边缘节点再由它转发到真正的GitHub拿到响应后再原路返回给用户。用户在浏览器里看到的始终是你的域名浑然不觉背后经过了中转。这和正向代理正好相反。正向代理是用户主动设置浏览器把请求交给代理网站转发所有流量都走代理属于“用户端”方案。反向代理则是把服务伪装成最终目标用户不用做任何配置访问你的域名就等于访问GitHub。GitHub镜像站显然更适合反向代理不需要用户安装东西、修改配置直接扔一个链接给别人就能用。代价是你得保证中间层层链路稳定还要处理各种跳转和内容替换问题。2.2 URL改写镜像站最容易翻车的环节有人觉得自己配好了反向代理页面也能打开就完事了。结果点登录按钮跳回了github.com或者页面里的CSS路径指向raw.githubusercontent.com变成一堆裸奔的HTML。这就是没做URL改写。镜像站需要做两层替换第一层响应头替换。GitHub的很多操作会返回30x重定向Location头里写的是原域名地址。如果不改用户一操作就飞出镜像域名等于断链。比如登录成功后跳到github.com/login/return_to镜像站必须把这个地址改写成自己的域名用户才能继续留在站内。第二层HTML内容替换。页面渲染出来的代码里有大量绝对路径比如https://github.com/octocat/Hello-World、https://avatars.githubusercontent.com/u/583231这些硬编码地址必须先替换成镜像域名再交给用户浏览器。否则页面加载过程中浏览器稍一遇到原域名连接失败图片裂掉、样式丢失、JS报错体验瞬间崩塌。所以一个成熟的镜像站不光要转发流量还要对响应体做文本替换。这也是后来自建服务器方案里配置sub_filter的原因。2.3 缓存策略决定体验上限GitHub页面内容其实非常“重”同一个HTML里可能塞着几十个外部资源请求。如果每次访问都回源镜像站响应慢GitHub也会因为请求量巨大触发限流得不偿失。缓存就是把重复请求的结果暂存在中间层下次直接返回不回源。但GitHub内容类型很杂缓存策略要分层HTML页面动态性强适合短TTL缓存比如一分钟。既能缓解回源压力又不至于页面更新了还看旧数据。静态资源头像、图标、CSS/JS基本不变缓存时间可以拉到一天甚至一周。release大文件体积大、下载多适合长缓存。但注意GitHub会限制免费额度个人搭建时一般不主动缓存超大文件。API请求api.github.com不能乱缓存涉及认证和实时数据缓存错了会出诡异问题。Cloudflare Workers等边缘计算平台天然自带缓存能力配置起来比自建Nginx省事得多这也是我选用它的重要原因。3. 方案选型三种常用路线对比搭建GitHub镜像站不是只有一条路。我实际尝试过三种主流方案各有鲜明的适用场景。3.1 Cloudflare Workers推荐个人首选Workers是Cloudflare的边缘计算服务代码部署在全球节点上用户请求落在哪个区域就在哪个区域直接响应天然自带CDN效果。它的优势很明显免费额度对个人够用每天10万次请求大多数私人镜像站根本跑不满。无需服务器不用买VPS、不用维护系统打开网页就能部署。自带缓存和限流Cloudflare的缓存规则、Rate Limiting规则能和Worker无缝配合。边缘网络覆盖面广国内访问质量相对稳定。劣势是国内直接访问*.workers.dev不稳定必须绑定自己的域名才能有效使用。另外免费版对请求体大小有限制超大release文件下载可能被截断需要取舍。3.2 自建服务器 Nginx自建方案需要一台能正常访问GitHub的服务器然后用Nginx反向代理 proxy_passsub_filter组合配置。优点是自由度极高带宽完全可控下载大文件不受平台限制可以针对特定域名做精细化路由还能自己写缓存策略、限流脚本、访问日志。缺点是维护成本大要监控服务器状态处理证书过期、磁盘爆满、流量超额等问题。服务器带宽贵如果给多人用流量费会心疼。此外不同国家对Nginx的sub_filter行为也有细节差异调试起来比Workers麻烦。3.3 Vercel / Netlify 等Serverless平台Vercel、Netlify也支持反向代理和边缘函数比如Vercel的Rewrite、Netlify的Proxy规则配置简单。实际测试感受是它们更适合做静态站点或轻量API转发处理GitHub这种“大文件 重定向 内容改写”的组合场景偏弱。Vercel的处理时常有超时限制下载大文件容易失败。只做网页浏览或许还行但clone和下载release体验很一般。3.4 我的推荐组合对绝大多数个人用户我的建议是Cloudflare Workers 自定义域名为主自建Nginx作为补充备份Vercel方案基本不用考虑。理由有四点成本低免费额度够用、部署快十分钟搞定、自带CDN省去自己折腾边缘节点、生态好Cloudflare的安全策略、缓存策略能直接叠加。如果确实有大文件下载需求可以考虑自建服务器单独代理codeload.github.com和objects.githubusercontent.com这两条链路其他流量继续走Workers。这样做还能避免单一Worker被超大请求拖垮。4. 实操用Cloudflare Workers搭建GitHub镜像方案定了就是动手。这部分我尽量把步骤写细致包括代码、配置、验证方式照着操作基本不会跑偏。4.1 准备域名与Cloudflare账号需要一个域名这是让镜像站被稳定访问的前提。把域名托管到Cloudflare或者在DNS服务商那里添加一条记录指向Cloudflare边缘节点都可以。核心是让Cloudflare能管理这个域名的DNS解析。在Cloudflare控制台的DNS设置里为镜像站准备两个子域名推荐直接用github.example.com代理github.com网页和Git协议raw.example.com代理raw.githubusercontent.com原始文件为什么不只用一个因为GitHub页面里有大量指向raw.githubusercontent.com的静态资源请求如果镜像站只有一个入口这些资源要么漏掉要么被统一塞进主域名导致路径冲突。拆成两个子域名映射两个上游逻辑干净排查问题也方便。4.2 编写Worker脚本进入Cloudflare控制台Workers Pages菜单下新建一个Worker粘贴下面的核心脚本。这个脚本做了三件事根据请求域名路由到不同上游、重写响应头里的Location、替换HTML页面里的原站链接。const ROUTES { github.example.com: https://github.com, raw.example.com: https://raw.githubusercontent.com }; const DOMAIN_MAP { https://github.com: https://github.example.com, https://raw.githubusercontent.com: https://raw.example.com }; addEventListener(fetch, event { event.respondWith(handleRequest(event.request)); }); async function handleRequest(request) { const url new URL(request.url); const upstream ROUTES[url.hostname]; if (!upstream) { return new Response(Unknown host, { status: 400 }); } const target upstream url.pathname url.search; const headers new Headers(request.headers); headers.set(Host, new URL(upstream).host); headers.delete(CF-Connecting-IP); headers.delete(X-Forwarded-For); const response await fetch(target, { method: request.method, headers, redirect: manual, cf: { cacheEverything: true, cacheTtl: 60 } }); const newHeaders new Headers(response.headers); // 步骤1改Location响应头避免重定向跳回原站 if (newHeaders.has(Location)) { let loc newHeaders.get(Location); for (const [from, to] of Object.entries(DOMAIN_MAP)) { loc loc.replace(from, to); } newHeaders.set(Location, loc); } // 步骤2对HTML内容做链接替换 const contentType newHeaders.get(Content-Type) || ; if (!contentType.includes(text/html)) { // 非HTML直接返回 return new Response(response.body, { status: response.status, headers: newHeaders }); } let body await response.text(); for (const [from, to] of Object.entries(DOMAIN_MAP)) { body body.split(from).join(to); } return new Response(body, { status: response.status, headers: newHeaders }); }这里几个细节值得展开说设置Host头是必须的。GitHub的vhost机制会根据Host判断该返回哪个站点的内容如果不设置请求可能落到默认站点结果乱七八糟。设置redirect: manual也很关键。如果让fetch自动跟随重定向Location响应头就永远到不了用户手里镜像站就失去了改写跳转的机会。HTML替换用的是split().join()而不是正则是出于性能考虑。正则替换大HTML字符串可能触发Worker的CPU限制朴素字符串操作更稳。实测这个脚本跑在GitHub的复杂页面上执行时间在可控范围内。4.3 配置路由规则Worker部署完成后回到Workers的页面找到Routes或“域”) 设置绑定两个域名github.example.com/*→ 绑定该Workerraw.example.com/*→ 绑定该Worker这样每次浏览器访问这两个域名时Cloudflare会自动触发Worker脚本不需要额外配置DNS转发规则。如果没有自定义域名Cloudflare也允许用*.workers.dev域名直接访问但国内访问稳定性差建议还是绑定自己的域名。4.4 绑定自定义域名与SSL绑定域名的操作在Workers的“Custom Domains”区域完成输入github.example.com等域名Cloudflare会自动添加一条CNAME记录指向Worker并申请SSL证书整个过程通常只需要几十秒。SSL模式建议选“Full (strict)”确保Cloudflare到你服务器的链路也加密。证书由Cloudflare自动管理不用自己续期这也是对比自建Nginx的巨大优势。4.5 验证与测试部署完成先别急着到处发按顺序做四轮测试第一轮浏览器直接访问github.example.com看仓库主页能否打开头像、图标是否正常显示。第二轮访问raw.example.com/用户/仓库/分支/文件名确认文件内容能直接拉取。第三轮测试Git clone。由于Git的智能HTTP协议本身会请求info/refs和POST /git-upload-pack而Worker脚本只是转发流量这一步通常能直接跑通。实测命令git clone https://github.example.com/octocat/Hello-World.git第四轮验证release下载。打开某个release详情页点击下载产物观察是否走通。如果浏览器能开页面、clone也能成功这个镜像站基本就立住了。如果某一环失败先别改代码按第五节的内容排查。5. 进阶缓存、限流、防滥用与稳定性基础镜像站跑通之后还要处理一个现实问题网站一旦能用就会有人分享给朋友甚至被一些爬虫盯上。没有防护措施Worker请求量会迅速增长最终被Cloudflare限额拦截或者被GitHub盯上封禁。5.1 缓存策略细化脚本里我设置了cacheEverything: true和cacheTtl: 60这会缓存所有响应60秒。对普通页面够用但有个隐患raw.example.com对应的raw文件内容是稳定不变的60秒缓存太短重复访问时回源压力大而HTML页面动态性强缓存60秒虽然勉强能用但仓库更新后自己看可能不是最新状态。更合理的做法是区分路由缓存策略。在handleRequest里加一段对hostname的判断const cacheConfig { github.example.com: { cacheEverything: true, cacheTtl: 60 }, raw.example.com: { cacheEverything: true, cacheTtl: 86400 } };这样网页保持短缓存raw文件缓存一天。对codeload.github.com的zip包下载Cache TTL需要根据文件大小灵活设置注意免费版Workers对单个响应体大小有限制。5.2 访问控制和限流Workers脚本本身可以做基础限流但更高效的办法是借用Cloudflare自带的WAF和Rate Limiting规则。在Cloudflare控制台的Security面板为github.example.com设置速率规则同一IP对/路径每分钟超过30次请求触发一会儿的封禁。对/download/或/archive/路径每分钟超过5次请求直接返回429。这是防滥用最直接的手段。还可以在Worker脚本里检查CF-IPCountry请求头拦截掉大部分恶意地区流量不过具体怎么配置取决于你对可用性的要求。另一个实用做法是加入简单的访问密码。在脚本里校验请求头中的自定义字段比如必须包含X-Access-Token: 你自己的密钥才放行。代价是所有访问者都需要手动加请求头只适合小范围分享不适合公开服务。5.3 日志与监控Cloudflare Workers自带日志功能在Worker的Logs标签页能看到实时请求日志包含路径、状态码、耗时、IP等。通过日志能发现异常请求模式比如某个IP刷了大量流量第一时间封掉。另外建议接一个免费的告警服务比如在Workers脚本里统计请求量接近免费额度阈值时通过邮件或Webhook通知自己。虽然有点折腾但跑着跑着突然被停运的感受谁试谁知道。5.4 稳定性与备份方案Workers平台本身稳定性很高但GitHub的上游波动会影响镜像站可用性。我遇到过一次GitHub侧短时故障镜像站跟着一片502这时候再多缓存也救不了。稳妥做法是配置fallback当main上游返回5xx时自动切换到备用上游镜像。比如Gitee或GitLab托管了同一仓库可以在Worker里加一层判断。当然这不是必须的个人镜像站对可用性要求没那么苛刻。还有一点容易被忽略Workers的免费额度是按天计算的当天超额后所有请求直接失败。如果发现访问量接近阈值要么升级为付费版要么严格限流要么加访问密码减少滥用。6. 常见问题与排查把我在真实使用中遇到的坑和排查思路整理成表按失效场景快速定位非常方便。现象可能原因排查与解决方法浏览器打开返回526上游SSL握手失败检查Cloudflare SSL为Full (strict)上游证书被拒绝可临时改为Flex观察返回502 Bad GatewayWorker回源失败查看Logs确认回源IP是否被GitHub限流可稍后重试或更换上游代理页面能开但图片全裂HTML替换不完整检查DOMAIN_MAP是否覆盖avatars.githubusercontent.com补充对应替换规则clone提示403 ForbiddenGit请求缺少User-Agent检查Cloudflare的Bot Fight Mode是否误伤Git客户端必要时关闭安全功能仓库更新后页面不刷新HTML被缓存增大缓存TTL设置或在Workers路由里对/raw/使用无缓存策略下载大文件失败超过Worker响应体限制将大文件下载流量切到自建Nginx单独代理codeload域名自定义域名打开是Cloudflare 404路由未绑定确认Routes里两个域名均指向Worker检查DNS解析生效6.1 502/526错误502和526本质都是回源失败。先看Worker日志确定是上游连接超时还是SSL握手失败。如果是GitHub侧波动等几分钟就好。如果是自己的Cloudflare配置问题重点检查SSL模式和回源地址是否写对。别忘了回源时设置的Host头GitHub对Host头极其敏感错了必挂。6.2 页面部分加载失败最常见的还是HTML替换没做全。GitHub的页面里除了github.com和raw.githubusercontent.com还可能出现avatars.githubusercontent.com、camo.githubusercontent.com等域名。逐个在DOMAIN_MAP里补上映射即可。另外某些CSS、JS资源如果走*.github.com的CDN而那个域名恰好国内直连不稳定也会出现局部加载失败。可以先开着浏览器Network面板看哪个请求4xx/5xx再针对性补替换规则。6.3 登录/跳转问题镜像站对未登录访问者基本友好但登录功能涉及大量30x重定向和Cookie跨域问题经常踩坑。第一个要点是Location改写必须完整第二个是镜像域名要设置可写Cookie。实测下来个人镜像站不建议开放登录功能容易触发GitHub的风控逻辑体验也远不如原站。保持只读共享即可。6.4 镜像站被刷/被滥用被刷其实是小概率但破坏力最大的事情。某次我把镜像链接发到群里第二天一看请求量翻了几十倍差点干穿免费额度。从此学乖了所有公开分享前先开启Rate Limiting重要路径加访问密码日志里异常IP直接封禁。宁可牺牲一点便捷性也不能让站点被塞爆。6.5 访问延迟高镜像站本身走Cloudflare边缘网络正常情况下延迟可控。如果某个地区访问明显偏慢通常是上游GitHub回源链路慢或者用户本身到Cloudflare的线路不好。可以用curl -w测一下各环节耗时定位是DNS、TLS还是回源慢再针对性调整。我自己的体会是GitHub镜像站的核心价值不在于“替代GitHub”而在于“优化访问链路”。对开发者个人而言Workers免费方案完全够用对团队或开源项目运营者建议在Workers基础上叠加Nginx备份双通道保障。这套组合下来clone速度、release下载体验、页面加载稳定性都比裸连GitHub强了一个量级。最后再提醒一句搭好之后务必要设置好访问控制和缓存策略防滥用和稳定性永远是镜像站运营的重头戏。
返回列表