必须显式禁用packagist.org,即在repositories数组末尾单独添加{"packagist.org": false},否则Composer仍会优先查询官方源;type字段须为"composer",URL末尾必须带/,且auth.json的host需与仓库URL完全一致。

内网源必须显式禁用 packagist.org,否则白名单形同虚设
哪怕你只 require 一个私有包,Composer 也会在拉取前先查 packagist.org —— 这是默认行为,不是 bug。白名单(即 repositories 数组)不会自动屏蔽官方源,它只是“多加几个查找位置”,而不是“替换查找顺序”。所以不关掉官方源,所有请求都会先撞墙。
正确做法是:在 composer.json 的 repositories 数组末尾,**单独加一个对象**:
{ "packagist": false }
这个开关必须满足三个条件才生效:
-
"packagist": false是独立对象,不能合并进其他仓库配置里 - 它必须放在数组最后,前面所有私有源(
satis、vcs、composer类型)都得排在它前面 - 它和
repositories同级,不能嵌套在config或require里
auth.json 的域名 key 必须和仓库 URL host 完全一致
内网源常走自建 GitLab、Nexus 或 Satis,它们的认证靠 auth.json 驱动,但 Composer 对 key 匹配极其严格:只比对 URL 的 host 部分,协议、端口、路径全忽略,也不做 DNS 解析或重定向判断。
比如你的仓库地址是 https://gitlab.internal:8443/group/repo.git,那么 auth.json 里必须写:
{ "gitlab.internal": { "http-basic": { "username": "...", "password": "glpat-xxx" } } }
以下写法全部无效:
-
"gitlab.internal:8443"(带端口) -
"https://gitlab.internal"(带协议) -
"gitlab.internal/group"(带路径)
而且 auth.json 只认两个路径:~/.composer/auth.json(Linux/macOS)或 %APPDATA%\Composer\auth.json(Windows),权限必须是 600,否则静默跳过。
type 和 URL 格式错一个字符,Composer 就直接跳过该仓库
内网源配置中,type 和 url 是硬性字段,拼错、少字符、多空格都会导致整条仓库注册失败,且不报错、不提示——它只是默默忽略你写的那一条。
常见错误包括:
- 对接 Satis/Nexus/Artifactory 等 HTTP 服务时,
type写成"vcs"或留空,必须是"composer" - HTTP 地址末尾漏
/:写成"https://satis.internal"会拼出packages.json路径为https://satis.internal/packages.json,而实际是https://satis.internal/packages.json/,结果 404 -
repositories写成对象而非数组:{"satis": {...}}是非法格式,必须是[{...}, {...}] - Git 仓库 URL 没带
.git后缀(如git@gitlab.internal:group/repo),type是"vcs"时必须带
换源后不删 vendor 和 composer.lock,白名单根本没机会运行
Composer 的 lock 文件记录了每个包的完整来源 URL 和 hash。哪怕你刚把 repositories 改成内网地址,只要 composer.lock 里还存着 https://repo.packagist.org 的路径,composer install 就会继续从旧源拉取——它连白名单都不看一眼。
所以每次切换内网源,必须执行:
-
rm -rf vendor composer.lock(Linux/macOS)或rd /s /q vendor && del composer.lock(Windows) - 再跑
composer install或composer update
CI/CD 流水线里尤其容易踩坑:如果缓存了旧 composer.lock,整个构建就卡在认证失败或 404,而日志里只显示 “Could not fetch”,看不出根源在哪。











