Satis构建后404的根本原因是Web服务器配置错误:Nginx root必须精确指向output-dir的绝对路径(如/var/www/satis/web),必须显式声明types { application/json json; },镜像URL必须以/结尾,且dist ZIP需可直连GET。

自建 Composer 镜像源不是“配个 URL 就能用”,它本质是静态元数据 + ZIP 包托管服务,必须同时满足 Web 可访问、路径可直连、MIME 类型正确三要素,否则 composer install 会静默 fallback 到 packagist.org 或直接 404。
为什么 Satis 构建完还是 404?
Satis 不是运行中的服务,只是生成一堆 JSON 和 ZIP 文件的工具。404 的真实原因几乎全是 Web 服务器配置问题:
- Nginx
root指向必须精确到satis.json中output-dir的绝对路径(例如/var/www/satis/web),不能多一级或少一级 - 必须显式声明 JSON MIME 类型:
types { application/json json; },否则返回text/plain,Composer 拒绝解析 - 镜像 URL 必须以
/结尾:比如"url": "https://pkgs.internal/",少斜杠会拼出/packages.json→/packages.json变成/packages.json,实际请求路径变成https://pkgs.internalpackages.json -
dist/下 ZIP 文件路径必须能被公网或内网 HTTP 直连 GET,常见拦截点:SELinux 限制、Nginxdeny all、文件权限为600
项目级 composer.json 怎么写才真正走私有源?
私有源必须写在你要装包的那个项目的 composer.json 里,不是 Satis 自己的配置文件,也不是包内部的 composer.json:
- 类型必须是
"type": "composer",URL 是托管packages.json的根地址(含末尾/) - 必须加上
"packagist.org": false,且放在repositories数组顶层,不是嵌套在某个仓库对象内部 - 私有源必须放在
repositories数组第一位 —— Composer 按顺序查找,不会合并同名源 - 如果已有其他私有 Git 源(
"type": "vcs"),不要用composer config repo.packagist ...命令,它会覆盖整个repositories字段;应手动编辑composer.json,把私有 Composer 源作为"packagist"键写入
换源后 composer install 还卡在 Downloading?
镜像只替换元数据入口(packages.json),不修改已锁定的 dist.url。ZIP 包仍从旧地址下载,而私有源的 ZIP 路径和官方完全不同:
-
composer.lock里硬编码了原始dist.url(如https://github.com/monolog/monolog/.../monolog-2.10.0.zip),必须删掉重生成 - 必须删掉
vendor/和composer.lock,再跑composer install(不是update)——update会复用旧 lock 文件里的哈希与路径 - 运行
composer show monolog/monolog --verbose查看当前dist.url,确认是否已变成你私有源下的路径(如https://pkgs.internal/dist/monolog/monolog/2.10.0/xxx.zip) - 别信日志里出现
Reading packages.json from cache就以为成功了 —— 缓存可能来自旧源,要盯住最终 ZIP 请求的域名
宝塔、CI、Docker 里镜像为啥还是不走?
全局配置(composer config -g)只对当前 shell 用户生效。宝塔后台用 www 用户、GitHub Actions 用 runner 用户、Docker 容器默认用非 root 用户 —— 它们都读不到你本地的 ~/.composer/config.json:
- 宝塔:进终端执行
sudo -u www composer config -g repo.packagist composer https://pkgs.internal/ - GitHub Actions:在 workflow 中加一步
run: composer config -g repo.packagist composer https://pkgs.internal/ - Docker:在
Dockerfile中写RUN composer config -g repo.packagist composer https://pkgs.internal/,或把预配置好的config.jsonCOPY 进容器的/root/.composer/ - 更推荐统一做法:所有环境都依赖项目级
composer.json配置,避免用户权限差异带来的不可控
最关键的细节往往藏在路径末尾那个 /、JSON MIME 类型声明、以及 composer.lock 是否被彻底清除 —— 这些地方一错,整个流程就静默退回到 packagist.org,连错误提示都不会给你。


















