Composer "Could not authenticate against github.com" (401) in CI
Composer fetches package metadata and zip archives from GitHub. Unauthenticated requests are rate-limited and private repos require a token. When Composer has no valid GitHub token, it returns HTTP 401 and prompts for credentials it cannot read in non-interactive CI.
What this error means
composer install fails with "Could not authenticate against github.com" or "The 'https://api.github.com/...' URL returned: 401". It commonly appears mid-build once the unauthenticated rate limit is hit.
Could not authenticate against github.com
Failed to download acme/widget from dist: The
"https://api.github.com/repos/acme/widget/zipball/abc123" file could
not be downloaded (HTTP/2 401 )Common causes
No GitHub token in non-interactive CI
Composer cannot prompt for credentials in CI, so private repos and rate-limited API calls fail with 401.
Unauthenticated GitHub API rate limit exhausted
Anonymous requests share a low hourly limit; busy CI bursts past it and GitHub starts returning 401/403 until it resets.
How to fix it
Provide a GitHub token to Composer
Configure an OAuth token from a secret so authenticated requests have a much higher rate limit and reach private repos.
composer config --global --auth github-oauth.github.com \
"${{ secrets.COMPOSER_GITHUB_TOKEN }}"
composer install --no-interactionUse the auth.json env variable
Pass credentials via COMPOSER_AUTH so nothing is committed.
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.COMPOSER_GITHUB_TOKEN }}"}}'How to prevent it
- Always provide a GitHub token to Composer in CI via COMPOSER_AUTH or github-oauth config.
- Use a least-privilege token and store it as a secret, never in the repo.
- Cache Composer downloads so fewer authenticated requests are needed per build.