GitGit

fatal: Authentication failed for 'https://github.com/'

GitHub stopped accepting account passwords over HTTPS in 2021. How to use a token or SSH, and clear the stale credential your OS keychain is still sending.

easy fix5 min read

the git error
remote: Support for password authentication was removed on August 13, 2021.
fatal: Authentication failed for 'https://github.com/acme/myapp.git/'

remote: HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled and you must use a personal access token instead of a password.
fatal: Authentication failed for 'https://gitlab.com/acme/myapp.git/'

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Check which protocol the remote uses

    git remote -v

    An https:// URL authenticates with a token; a git@ URL uses SSH keys. Most of these failures are an HTTPS remote still being handed a password that the host no longer accepts.

  2. 2

    Clear the stale credential your system is caching

    printf 'protocol=https\nhost=github.com\n\n' | git credential reject

    Git will not prompt again while a helper holds a cached credential, so entering a new token has no effect until the old one is removed. This works with whichever helper is configured.

  3. 3

    Or switch the remote to SSH and stop dealing with tokens

    git remote set-url origin git@github.com:acme/myapp.git && ssh -T git@github.com

    SSH keys do not expire the way tokens do. The ssh -T check prints a greeting with your username when the key is accepted.

All 8 sections

GitHub removed password authentication for Git over HTTPS on 13 August 2021. GitLab and Bitbucket did the same. An HTTPS remote now needs a personal access token in place of the password, or you switch to SSH.

git remote -v
origin  https://github.com/acme/myapp.git (fetch)
origin  https://github.com/acme/myapp.git (push)

https:// means token. git@github.com: means SSH key.

The stale credential problem

This is what makes the error persist after you have created a token. Your operating system cached the old credential, and Git keeps sending it without prompting.

git config --get credential.helper

Clear it in a helper-agnostic way:

printf 'protocol=https\nhost=github.com\n\n' | git credential reject

Or per platform:

# macOS keychain
git credential-osxkeychain erase <<EOF
protocol=https
host=github.com
EOF

# Linux, libsecret
git credential-libsecret erase <<EOF
protocol=https
host=github.com
EOF

On Windows, Credential Manager under Control Panel has entries named git:https://github.com.

Until the old entry is gone, a correct new token appears not to work, which sends people round in circles.

Fix 1: Personal access token

Create one at Settings → Developer settings → Personal access tokens. Fine-grained tokens are preferred: they are scoped to specific repositories and have an expiry.

The classic-token scope you need is repo. For a fine-grained token, Contents: Read and write, plus Metadata: Read which is mandatory.

Then push and paste the token as the password:

git push
# Username: your-github-username
# Password: github_pat_11ABCDEFG...

Store it so you are not pasting it every time:

# macOS
git config --global credential.helper osxkeychain

# Linux with a desktop keyring
git config --global credential.helper libsecret

# Cross-platform, Microsoft's manager
git config --global credential.helper manager

Avoid credential.helper store. It writes the token in plain text to ~/.git-credentials with no encryption at all.

credential.helper cache keeps it in memory for 15 minutes, which is a reasonable middle ground on a shared machine:

git config --global credential.helper 'cache --timeout=3600'

Fix 2: SSH, which is less maintenance

Keys do not expire. Once set up, it stays working.

ssh-keygen -t ed25519 -C "you@example.com"
cat ~/.ssh/id_ed25519.pub

Add the public key at Settings → SSH and GPG keys, then:

git remote set-url origin git@github.com:acme/myapp.git
ssh -T git@github.com
Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.

That message means success, despite how it reads.

If your network blocks port 22, GitHub serves SSH over 443:

# ~/.ssh/config
Host github.com
  Hostname ssh.github.com
  Port 443
  User git

CI and automation

Never a personal token in a pipeline. Use the platform's own:

# GitHub Actions: actions/checkout configures credentials automatically
- uses: actions/checkout@v4

# pushing back needs write permission
permissions:
  contents: write
# GitLab CI, CI_JOB_TOKEN is injected
script:
  - git remote set-url origin "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/${CI_PROJECT_PATH}.git"
  - git push origin HEAD:main

For a server or a deploy script, a deploy key scoped to one repository is better than an account token, because it cannot reach anything else.

SAML and organisation restrictions

remote: The `acme' organization has enabled or enforced SAML SSO.

A token has to be authorised for a SAML-protected organisation separately from being created. On the token's settings page there is a Configure SSO button. A perfectly valid token fails against that organisation until you use it.

Fine-grained tokens expire

This is the modern version of the problem. Fine-grained tokens have a maximum lifetime and expire silently; the first sign is this error appearing on a machine that worked yesterday. If it is a machine you rarely touch, SSH is the better choice precisely because there is nothing to renew.

A checklist

  1. git remote -v. https:// → token. git@ → SSH key.
  2. Clear the cached credential first, or a new token will appear not to work.
  3. Create a fine-grained token with Contents: Read and write, plus Metadata: Read.
  4. Use it as the password, with your username as the username.
  5. Store it in a keychain helper, not credential.helper store.
  6. Prefer SSH for long-lived machines. Keys do not expire.
  7. Port 22 blocked → ssh.github.com on 443.
  8. CI → the platform's job token or a deploy key, never a personal one.

Frequently Asked Questions

Why did my GitHub password stop working?

GitHub removed support for account passwords on Git operations over HTTPS on 13 August 2021, as part of requiring stronger authentication. The password field now expects a personal access token instead, which can be scoped to specific repositories and given an expiry, so a leaked one is far less damaging than an account password. GitLab and Bitbucket made equivalent changes. The alternative is to switch the remote to SSH, which uses a key pair and sidesteps the whole question.

Why does my new token still fail?

Because a credential helper is caching the old one and Git never prompts you for the new one. macOS Keychain, Windows Credential Manager and libsecret all persist the previous entry, so Git keeps sending a credential the host has already rejected. Clear it with printf 'protocol=https\nhost=github.com\n\n' | git credential reject, which works with whichever helper is configured, then push again and enter the new token when prompted. This single step resolves most cases where a correct token appears not to work.

Should I use a token or SSH?

SSH for any machine you will keep using, because keys do not expire and there is nothing to renew. Tokens for short-lived contexts, and for situations where SSH is blocked by a network that only allows outbound 443, although GitHub also serves SSH over 443 via ssh.github.com. Tokens have the advantage of fine-grained per-repository scoping, which makes them the better choice for a script that should only reach one repository. For CI, use neither: use the platform's job token or a deploy key.

What scopes does my token need?

For a classic token, repo covers cloning, pulling and pushing to private repositories. For a fine-grained token, select the specific repositories and grant Contents: Read and write, remembering that Metadata: Read is mandatory and easy to miss. Add Workflows: Read and write only if you need to push changes to files under .github/workflows, which is refused without it. Grant the minimum that works, since a token with repo on a classic grant can reach every repository your account can.

Why does my valid token fail against one organisation only?

That organisation has SAML single sign-on enforced, and a token must be explicitly authorised for it in addition to being created. On the token's settings page there is a Configure SSO control that grants it access per organisation. Until you use it, the token works normally everywhere else and is rejected by that one organisation, which makes the failure look like a token problem rather than an authorisation one.

Reference and practice

Learn the underlying concept

Other Git errors