根本原因是gitlab自签名证书不在php信任链中;需在composer.json的repositories中为该仓库配置绝对路径的ssl.cafile,指向含完整证书链的pem文件,并确保各环境可读。

Composer报SSL certificate problem:自签名证书不被信任
直接原因是 Composer 用 PHP 的 stream 或 curl 发起 HTTPS 请求时,发现 GitLab 的自签名证书不在系统 CA 信任链里,它不会自动跳过验证,也不会降级到 HTTP——这是安全机制,不是 bug。
你看到的错误通常是:
SSL certificate problem: unable to get local issuer certificate
或更具体的:
The "https://gitlab.example.com/api/v4/projects/..." file could not be downloaded (cURL error 60)
解决思路不是关 SSL,而是告诉 Composer “该信谁”。关键在 sslcafile 配置,且必须指向一个有效的 PEM 文件(含完整证书链)。
-
sslcafile必须是绝对路径,且文件在所有运行 Composer 的机器上都可读(CI 节点、本地开发机、Docker 容器内都要存在) - 不能只放根证书,要包含中间证书和服务器证书拼在一起的完整 PEM;可用
openssl s_client -connect gitlab.example.com:443 -showcerts提取 - 权限必须是 644 或更严格(如 600),否则 Composer 会静默忽略
- 如果团队多环境,建议统一放在项目根目录(如
./certs/gitlab-ca.pem),并在composer.json中显式引用
repositories 中配置 sslcafile 最可靠
全局配 cafile 看似省事,但容易污染其他项目;为私有仓库单独配 sslcafile 更安全、更可控,尤其当你同时对接多个私有源(比如 GitLab + Nexus + 自建 Satis)时。
在 composer.json 的 repositories 数组中,给对应 GitLab 源加 options:
{
"repositories": [
{
"type": "vcs",
"url": "https://gitlab.example.com/group/project.git",
"options": {
"ssl": {
"cafile": "/absolute/path/to/gitlab-ca.pem"
}
}
}
]
}
注意几点:
-
cafile值必须是绝对路径;相对路径(如./certs/ca.pem)在某些 PHP 版本或容器环境下会失效 - 这个配置只影响该仓库,不影响 Packagist 或其他
repositories条目 - 如果用了 Docker,确保
cafile路径在容器内真实存在,并已通过VOLUME或COPY挂载进去 - 改完后执行
composer clear-cache,否则旧缓存可能掩盖证书加载失败
为什么 auth.json 和 gitlab-token 配置没用?
因为 auth.json 或 gitlab-token 只管认证(HTTP Basic / Token),不管 TLS 握手。证书校验发生在建立 HTTPS 连接的第一步,早于任何认证环节。哪怕 token 完全正确,只要证书不被信任,请求根本发不出去。
常见误判场景:
- 看到
401 Unauthorized就去调auth.json,结果真正卡在前一步的 SSL 握手失败,日志里却只显示模糊的Could not fetch - 用
curl -k https://gitlab.example.com能通,就以为没问题——但-k是跳过验证,Composer 不支持这个开关 - 把公司内网 CA 证书装进系统信任库(如
/etc/ssl/certs),但 PHP CLI 用的是自己编译时绑定的 OpenSSL 目录,不一定读系统 CA
验证是否真由证书导致:临时加 "secure-http": false 到 composer.json 的 config 段,再跑 composer install。如果这时能走通 HTTP(前提 GitLab 开了 HTTP 端口),那基本锁定是证书问题。
GitLab 实例启用了端口或子路径怎么办?
比如 GitLab 地址是 https://gitlab.example.com:8443 或 https://gitlab.example.com/gitlab,sslcafile 配置本身不受影响,但要注意两件事:
-
repositories.url必须带端口号(如https://gitlab.example.com:8443/group/project.git),否则 Composer 请求会打到默认 443,证书域名不匹配 -
auth.json中的域名 key 必须与 URL 主机名+端口完全一致(如"gitlab.example.com:8443"),少一个字符都不行;http-basic的 key 也一样 - 如果用了子路径(如
/gitlab),url仍按克隆地址写(即https://gitlab.example.com/gitlab/group/project.git),但证书通常是签给gitlab.example.com的,所以sslcafile不需要额外调整
最易忽略的一点:证书里的 Subject Alternative Name(SAN)必须包含你实际访问的完整主机名(含端口不算,但含子域名必须匹配)。如果证书只签了 gitlab.example.com,而你用 gitlab.internal.example.com 访问,即使 DNS 解析通,也会因 SAN 不匹配而失败。











