Composer "COMPOSER_AUTH" / auth.json Missing - 401 on Private Packages
Composer authenticates to private registries and Git hosts using auth.json or the COMPOSER_AUTH environment variable. When neither is present in CI, requests to a private source come back 401/403 and the install fails.
What this error means
Composer fails on a private package or registry with an HTTP 401/403, or prompts for credentials it cannot read non-interactively. auth.json exists locally (and is gitignored), but CI has nothing.
- Downloading acme/private (2.0.0)
The "https://repo.acme.dev/.../acme-private-2.0.0.zip" file could not be
downloaded (HTTP/2 401): authentication required
Invalid credentials for 'repo.acme.dev', aborting.Common causes
auth.json is gitignored and absent in CI
Credentials live in a local, gitignored auth.json. CI never receives it, so private requests are unauthenticated and rejected.
COMPOSER_AUTH is unset or malformed
The COMPOSER_AUTH env var is the CI-friendly way to pass credentials, but if it is missing, has bad JSON, or names the wrong host, Composer cannot authenticate.
How to fix it
Pass credentials via COMPOSER_AUTH
Provide a JSON blob from a CI secret. Composer reads it without any committed file.
export COMPOSER_AUTH='{"http-basic":{"repo.acme.dev":{"username":"ci","password":"'$REPO_TOKEN'"}}}'
composer install --no-interactionOr write an auth.json from a secret
If you prefer the file form, materialise it from a secret in the job, not the repo.
composer config --auth http-basic.repo.acme.dev ci "$REPO_TOKEN"
# writes ./auth.json (or use --global)Confirm the host key matches the registry
- The JSON key under
http-basicmust equal the registry host exactly. - Validate the JSON with
php -r "json_decode(getenv('COMPOSER_AUTH'));". - Re-run with
-vvvto see which host returns 401.
How to prevent it
- Inject
COMPOSER_AUTHfrom CI secrets for every private source. - Keep
auth.jsongitignored; never commit live credentials. - Use a least-privilege, rotatable token scoped to the registry.