没人喜欢给 Mac 换镜像源,但每个用 Homebrew 超过三个月的人,迟早都会被那根卡死不动的进度条逼到这一步。我自己的经历是:某天晚上执行 brew install 一个纯命令行工具,Downloading 阶段等了 8 分多钟,最后 curl 超时报错重来,一怒之下决定把官方源换成中科大或阿里云的镜像源。真正动手之后我才意识到,Homebrew 慢并不是一个 URL 的问题,而是它拿数据的方式本身分了层,每一层各有各的慢法。这篇就把我怎么判断瓶颈、怎么换源、怎么验证,以及换源时踩过的几个坑完整写出来,给同样被官方源拖到难受的人一个可以直接照做的方案。
1. 慢的根源:Homebrew 的数据获取从来不是一条路
Homebrew 虽然看起来只有一个命令,但它背后其实是从三个完全不同的位置拿数据。搞明白这三条路径,比直接复制十行 export 有用得多,因为后续所有换源操作,本质都是围绕这三条路径分别替换地址。
1.1 三层数据来源,三种不同的慢法
第一层是 brew 命令本体所在的仓库。Homebrew 自己是用 git 管理的,官方 origin 指向 GitHub 上的 Homebrew/brew 仓库。每次执行brew update,它都要先到这个仓库做一次 git fetch,虽然这个仓库不算大,但网络质量差的时候照样能卡很久。
第二层是 formula 和 cask 的元数据。在老版本里,这一层是一个叫 homebrew-core 的 git 仓库,里面有大量 formula 文件,体积接近"半个 GitHub 仓库"的规模。传统方案里brew update要全量拉取它,这也是早期 Homebrew 慢得人尽皆知的直接原因。从 4.0 开始,Homebrew 改成了按需下载 JSON API 的元数据文件,把这一层的数据量从"git 全量仓库"降到了"几个 MB 的 json"。慢是缓解了,但 API 域名依然架在海外,网络不乐观的时候照样卡。
第三层是 bottle,也就是预编译好的二进制包。brew install真正下载的大头都在这一层,一个包几十到几百 MB 都常见。bottle 默认从 ghcr.io 拉取,这个域名的连接质量如何,凡是拉过大包的人应该都深有体会。
关键结论是:update 慢主要是第一、二层的事,install 慢主要是第三层的事。只换其中一层,症状最多缓解一半,很多人换了镜像还是慢,就是因为只解决了 bottle 这一层,却没管 API 层和仓库层。
1.2 先用一条命令定位你到底慢在哪一层
很多人在没搞清楚瓶颈的情况下就乱换源,结果换完镜像还是慢,就开始怀疑镜像有问题。正确做法是先定位再做决策。
time brew update time brew install jq如果brew update明显卡顿,说明 git 仓库和 API 这两层有问题;如果 update 很快,但 install 在大尺寸 bottle 的 Downloading 阶段一动不动,那瓶颈基本就在 ghcr.io 上。
另外一个更直接的观察方式是用-v参数:
brew install jq -v输出里会明确打印出它正在访问的 URL。你看到ghcr.io/v2/homebrew/core/jq/...这类地址的时候,就说明包下载走的是官方,还没被镜像接管。看到具体的 URL,比任何猜测都可靠。
2. 换源之前的三项体检:架构、版本、旧仓库残留
换源不是复制一段配置就能收工。Homebrew 的"版本状态"不同,同一套镜像配置的落点完全不一样。我见过太多人把 3.x 时代的教程往 4.x 上套,改了半天 origin,结果发现本地根本没有那个仓库,白忙活一场。
2.1 先确认你的 Mac 是哪个架构
架构决定了 Homebrew 装在哪,也决定了你后面操作哪个目录。
uname -marm64:Apple Silicon(M1/M2/M3 系列),Homebrew 前缀一般在/opt/homebrewx86_64:Intel 芯片,Homebrew 前缀一般在/usr/local
拿不准直接执行brew --repo,它会把当前仓库的实际路径打出来。后续所有"cd 到 brew 仓库"的操作,都以你自己的brew --repo输出为准,别硬记/opt/homebrew还是/usr/local,版本迭代过程中这些路径细节很容易让人记混。
2.2 版本决定换源方式:3.x 看 git remote,4.x 看环境变量
执行brew --version看一眼大版本。如果是 3.x,你的本地大概率存在 homebrew-core 这个 tap 目录,换源时需要连 git remote 一起改,否则 update 依然会按旧地址去 GitHub 全量拉取。如果是 4.x,默认走 JSON API,本地不会新建 core 仓库,重点就落在 API 域名和 bottle 域名的配置上。
怎么看本地有没有残留的 core/cask 仓库:
ls "$(brew --repo)/Library/Taps/homebrew"如果输出里有homebrew-core或homebrew-cask字样,说明这台机器是从老版本一路升上来的,或者曾经手动执行过brew tap。有残留就按第三节里的 git remote 方案一起改;没有残留,就只用环境变量方案,事情会简单很多。
另外提一句,4.x 其实也保留了"回到 git 模式"的开关HOMEBREW_INSTALL_FROM_API,默认是走 API 的,一般不需要动它。但如果你发现本地明明有 core 仓库、却总感觉 update 行为不太对,可以查一下这个变量的值,它会在某些边缘场景下改变 Homebrew 的数据获取方式。
2.3 动手前先抄一份"底账",方便后悔
换源属于低风险操作,但不是零风险。在改动之前,我强烈建议先把关键信息记下来,方便日后还原:
git -C "$(brew --repo)" remote -v这条命令会展示 brew 本体现在接的 origin 地址,正常情况下是https://github.com/Homebrew/brew.git。如果你原来已经换过源,这里显示的就是之前某个镜像的地址。抄下来,后面想回官方源的时候直接照着 set-url 就行,比我下面给的各种还原命令更贴合你自己的机器状态。
3. 中科大与阿里云镜像的完整配置步骤
现在进入正题。核心思路很简单:用环境变量把 Homebrew 的 API、bottle、git 仓库三个出口全部指到国内镜像。下面分两个镜像分别给全配置,顺带把老版本需要的 git remote 操作也补上。
3.1 中科大镜像:export 四连,路径最直观
中科大的仓库路径是国内镜像里最"直给"的,没有多余前缀。在终端先手动 export 验证:
export HOMEBREW_API_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles/api" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles" export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git"解释一下这四个变量的职责:
HOMEBREW_API_DOMAIN:替换 formula/cask 元数据的 JSON API 地址,对应官方https://formulae.brew.sh/apiHOMEBREW_BOTTLE_DOMAIN:替换预编译包的下载地址,对应官方 ghcr.ioHOMEBREW_BREW_GIT_REMOTE:替换 brew 本体仓库的地址HOMEBREW_CORE_GIT_REMOTE:替换 core 仓库地址,主要对老版本和手动 tap 场景生效
手动 export 只是当前终端临时生效,关掉窗口就没了。确认没问题之后,要写进 shell 配置文件永久保存,具体写到哪看后面的 3.4 节。
3.2 阿里云镜像:同样的四连,路径多一层 homebrew
阿里云的镜像结构和中科大不完全一样,所有仓库都放在/homebrew/这个前缀下面,第一次配的人特别容易在这里犯 404 的错。
export HOMEBREW_API_DOMAIN="https://mirrors.aliyun.com/homebrew/homebrew-bottles/api" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.aliyun.com/homebrew/homebrew-bottles" export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/brew.git" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/homebrew-core.git"两个镜像的关键路径对比如下,方便你一眼看出差异:
| 配置项 | 中科大 | 阿里云 |
|---|---|---|
| brew 仓库 | mirrors.ustc.edu.cn/brew.git | mirrors.aliyun.com/homebrew/brew.git |
| core 仓库 | mirrors.ustc.edu.cn/homebrew-core.git | mirrors.aliyun.com/homebrew/homebrew-core.git |
| bottle 域名 | mirrors.ustc.edu.cn/homebrew-bottles | mirrors.aliyun.com/homebrew/homebrew-bottles |
| API 域名 | mirrors.ustc.edu.cn/homebrew-bottles/api | mirrors.aliyun.com/homebrew/homebrew-bottles/api |
两个镜像怎么选?我的建议是:如果你的网络环境访问教育网资源比较快,或者本身就在学校、研究所网段里,优先中科大。如果是普通宽带或者云主机,阿里云的大带宽通常更稳。两个镜像的同步频率都不低,日常使用体感差距不大,哪个顺手用哪个,随时可以切。
另外提一句,清华 TUNA 也维护了一套同类的 Homebrew 镜像,配置思路一模一样,只是域名不同。这篇不展开,你只要理解了环境变量对应的地址格式,换成任何镜像都是一分钟的事。
3.3 本地还留着 core/cask 仓库?git remote 也要一起改
如果你的机器是老版本升上来的,或者曾经手动 tap 过 core/cask,光设环境变量还不够,brew update依然会按照本地 git 仓库里记录的 origin 去拉数据。这一步是把仓库的远程地址直接指到镜像。
cd "$(brew --repo)" git remote set-url origin https://mirrors.ustc.edu.cn/brew.git再看有没有 core 和 cask 的本地仓库,有就一起改:
git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git改完记得用git remote -v复查一遍。阿里云版本只要把 URL 换成上面 3.2 节里的对应地址即可。这一步做完,换源才算真正"闭环"。
提示:
brew --repo homebrew/core这个写法只有在本地确实存在该 tap 时才有意义。本地不存在时它会报错提示无效的 tap 名,看到报错别慌,说明你的环境根本不需要改它,跳过就好。
3.4 写入 shell 配置,让新终端也保持镜像
临时 export 只对当前终端有效,开一个新窗口就没了。macOS 默认 shell 是 zsh,所以配置文件一般是~/.zshrc。用追加方式写进去:
cat >> ~/.zshrc <<'EOF' export HOMEBREW_API_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles/api" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles" export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git" EOF然后执行source ~/.zshrc,或者干脆开一个新的终端窗口。如果你用的不是 zsh,而是 fish 之类的 shell,语法要换成 fish 自己的:
set -gx HOMEBREW_API_DOMAIN "https://mirrors.ustc.edu.cn/homebrew-bottles/api"写完别急着安装软件,先做一步验证:
env | grep HOMEBREW能看到你刚才设置的四个变量,说明配置已经被当前 shell 加载。看不到就去检查是不是 source 失败了,或者变量写进了错误的配置文件。
4. 换源后的效果验证:别凭感觉说快,用数据说话
换完源最怕的就是"感觉好像快了,又好像没快"。建议按下面三步走完,每一步都有明确输出,能让你清楚地知道配置到底生效没有。
4.1 先看 brew config 的输出
brew config这个命令会打印 Homebrew 当前的核心配置。重点看两个位置:一是ORIGIN字段,正常应该显示你设置的镜像仓库地址,比如https://mirrors.ustc.edu.cn/brew.git;二是输出里的有关 Homebrew 环境变量段,确认 API 和 bottle 域名都是镜像地址。
如果ORIGIN显示的依然是 GitHub 官方地址,说明 brew 仓库本体的远程还没改对,回到 3.3 节检查 git remote。这一步排查对"换完源还是慢"的情况特别管用,因为环境变量能改的是行为逻辑,本地 git 的 origin 决定的是实际请求打到哪台服务器上。
4.2 用 curl 直接测两个镜像的下载速度
与其反复 install 测试,不如先用 curl 看原始速度。这里选一个固定文件来对比:
curl -o /dev/null -s -w "下载时间: %{time_total}s, 平均速度: %{speed_download} 字节/秒\n" https://mirrors.ustc.edu.cn/homebrew-bottles/api/formula.jws.json把 URL 换成阿里云的对应地址再跑一次,顺手对比一下两个镜像在你当前网络下的速度。我实测下来,多数情况下国内镜像能把速度从"几十 KB/s 的蠕爬"拉到"几 MB/s 起跳",如果宽带条件好,差距会看得更清楚。这个文件本身不大,测的是连接质量和首包速度,代表的是日常元数据请求的体感;真正评估大包下载,还是得靠实际安装。
4.3 实际安装一个包,看整体耗时和日志行
测速只能说明链路快,最终还是要回归到brew install的整体体验:
time brew install jq第一次运行时,Homebrew 会额外下载 JSON API 索引,可能比平时慢一点点,这是正常现象,下一次安装就会快很多。装一个几十 MB 的包,对比换源前后的体感最明显。
另外注意:brew update的输出也会变得干净利落。以前可能是卡在 "Updating Homebrew..." 半天不动,换源之后几秒内就能完成。如果 update 之后提示Already up-to-date,先别怀疑,这是正常状态,说明你的仓库已经和镜像源同步到最新了。
5. 换源路上的坑:我把常见翻车现场都替你过了一遍
换源本身不复杂,但真动手的时候,几乎每个人都会在下面这几个地方至少卡一次。我把翻车场景和排查思路都列出来,你遇到问题直接对号入座。
5.1 404/403 报错:九成是域名路径没对齐
最常见的报错长这样:curl: (22) The requested URL returned error: 404,位置卡在 Downloading 阶段。第一反应别去怀疑镜像挂掉了,先检查是不是把两个镜像的路径拼错了。
典型错误就是把中科大的路径直接套到阿里云域名后面,比如写了https://mirrors.aliyun.com/homebrew-bottles/api,真实地址却在homebrew目录下面。404 的本质就是"地址不存在",把表里的路径逐字对一遍,通常就能找到问题。
还有一种情况是 API 域名和 bottle 域名没有成对设置。比如只设置了HOMEBREW_BOTTLE_DOMAIN,API 域名还留在官方,Homebrew 从官方 API 拿到元数据后,再按镜像路径去下载 bottle,两边版本信息一旦有细微差异,就可能算出一个镜像上不存在的 URL,表现同样是 404。所以四个环境变量不要只挑两三个设,一次性设全,最省事。
5.2 环境变量管住的是"新克隆",本地 origin 才是老大
这是我换源过程中最有价值的一个发现。很多教程只教设置HOMEBREW_BREW_GIT_REMOTE,但对已经安装在机器上的 Homebrew 来说,这个变量主要影响的是安装脚本和新建克隆时使用的远程地址。你本地的 brew 仓库是早期 clone 下来的,它的 origin 还指向 GitHub,运行brew update时,实际走的还是本地 git remote 那条路。
所以判断 update 慢的问题时,先看本地仓库的 origin:
git -C "$(brew --repo)" remote -v如果显示 GitHub 官方地址,光有环境变量没用,必须执行 3.3 节的git remote set-url。这也是"换完源还是慢"这个高频问题的最常见原因,我自己第一次就栽在这上面:环境变量配得漂漂亮亮,结果 update 依旧磨蹭,查了半天才发现本地 origin 根本没动。
5.3 写了 export 却不生效,按这个顺序排查
如果你确认自己写了配置,新终端里env | grep HOMEBREW却空空如也,按下面顺序查:
- 确认配置文件写对了。macOS 默认 zsh 读
~/.zshrc,如果你用 bash 却写进了.bash_profile,主 shell 是 zsh 的话根本不会加载。 - 确认写的是 export,不是裸赋值。
HOMEBREW_API_DOMAIN="xxx"在 shell 里只是普通变量,不会传给子进程。 - 确认写完执行过
source ~/.zshrc,或者真的开了一个新窗口。很多终端软件会复用旧会话,环境不会自动刷新。 - 如果用 fish 这类非 POSIX shell,
export语法不适用,要用set -gx。
这四条走完,99% 的"不生效"问题都能解决。剩下的 1%,基本是终端启动了多个 shell 环境,比如某些 IDE 内置终端只继承了 IDE 启动时的环境变量,这种直接重启 IDE 就好。
5.4 镜像同步有延迟,新版本 bottle 偶尔会 404
镜像再好,本质上也是定时从官方同步,不可能做到实时。某天你 brew install 一个新发布的版本,镜像上可能还没有对应的 bottle,这时报 404 很正常,不代表你的配置出了问题。
我的处理方式是:先等一下,镜像的同步周期通常以小时计,过几个小时再试。等不及就临时绕开镜像只装这一次:
unset HOMEBREW_BOTTLE_DOMAIN brew install <那个包>装完再把环境变量 source 回来。不建议为了这一个包改成源码编译,那才是真的又慢又容易踩新的坑。如果频繁遇到这种 404,可以换个镜像试试,不同镜像的同步节奏不完全一致,有些包在这个镜像上没有,在另一个镜像上可能已经有了。
6. 回归官方源和日常使用的几个小习惯
换源不是一劳永逸的事,但也不是什么高风险操作。什么时候要回官方源?比如你此后要长期访问海外网络环境,或者镜像源临时抽风,这时候需要把改动撤销掉。
6.1 一键还原官方源
还原分两步:先把 git remote 改回官方,再清理环境变量。改 remote:
cd "$(brew --repo)" git remote set-url origin https://github.com/Homebrew/brew.git如果有本地 core/cask 仓库,同样把 URL 改回github.com/Homebrew/homebrew-core.git和github.com/Homebrew/homebrew-cask.git。然后去~/.zshrc里删除那几个 export 行,source ~/.zshrc即可。改完后执行一次brew update,看到它正常走官方源就算还原成功。
6.2 让 Homebrew 日常更快的小建议
最后分享几个我一直在用的习惯,配合镜像源组合起来效果更好:
- 给
brew install加上环境变量HOMEBREW_NO_AUTO_UPDATE=1,可以省掉每次安装前自动 update 的等待。注意这只是跳过自动检查,你仍然应该定期手动执行brew update,否则 formula 版本会越来越陈旧。 - 定期执行
brew cleanup清理旧版本的 bottle,你会发现磁盘占用比想象中还容易堆积。 - 新机器装 Homebrew 时,可以把
HOMEBREW_BREW_GIT_REMOTE和HOMEBREW_CORE_GIT_REMOTE预先 export 再跑官方安装脚本,装完直接就是镜像源,省得再改一遍。 - cask 安装的 GUI 应用,很多是直接从软件厂商官网下载安装包的,这一部分镜像管不到,下载慢的话别把锅扣在换源方案头上。
我个人折腾下来的最终选择是中科大的镜像,主要因为它路径结构清晰,我所在网络环境下访问它也确实稳定。但从实际操作来看,阿里云的表现同样可靠,关键不是选哪一个,而是搞清楚分层配置的逻辑,别在一个地方用死理,也别指望换完源就一劳永逸。镜像同步有延迟、本地 origin 没改干净、环境变量写错文件,这些坑我都替你踩过了,照着上面的流程走,基本不会有第二个难眠的深夜。