Git Credential Managers: Secure Authentication
Git does not store credentials itself. It delegates to credential helpers — external programs that supply, save and erase credentials on request.
Understanding that separation explains everything else: why some setups prompt on every push, why others never do, and why one particular helper writes your token to a plain-text file.
How the subsystem works
Section titled “How the subsystem works”When Git needs credentials for an HTTPS remote, it works through a sequence:
- A configured credential helper, if one can supply them.
GIT_ASKPASS, if set.core.askPass, if configured.SSH_ASKPASS, if set.- A terminal prompt, as the fallback.
A helper is just a program. credential.helper = manager makes Git run git credential-manager, passing
it one of three operations on standard input:
| Operation | Meaning |
|---|---|
get | Supply credentials for this host, if you have them |
store | Remember these credentials |
erase | Forget credentials for this host |
That is the entire contract. Any program implementing it can be a helper, which is why platform keychains, enterprise single sign-on systems and password managers can all plug into Git.
You can drive it directly, which is a useful way to see the mechanism:
printf 'protocol=https\nhost=github.com\n\n' | git credential fillGit asks each configured helper in turn, then prompts if none can help.
The helpers
Section titled “The helpers”| Helper | Platform | Storage | Security |
|---|---|---|---|
manager | Cross-platform, bundled with Git for Windows | OS credential store | Good |
osxkeychain | macOS | Keychain | Good |
libsecret | Linux with a keyring | Keyring via D-Bus | Good |
wincred | Windows | Windows Credential Manager | Good |
cache | Any | Memory only, time-limited | Good |
store | Any | Plain-text file | Poor |
# In-memory, expires after an hour — no disk storage at allgit config --global credential.helper 'cache --timeout=3600'For persistent storage, the libsecret helper integrates with your desktop keyring. Distributions ship
its source rather than a binary, so it is built once:
sudo apt install build-essential pkg-config libsecret-1-devsudo make --directory=/usr/share/doc/git/contrib/credential/libsecretgit config --global credential.helper \ /usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecretCheck /usr/share/doc/git/contrib/credential/ on your own system — the path is set by your git package
and differs between distributions.
On a headless server with no keyring, cache is the right choice. Do not fall back to store.
git config --global credential.helper osxkeychainCredentials go into the macOS Keychain, encrypted and protected by your login. The helper ships with both Apple’s Git and Homebrew’s.
To remove a stored credential after rotating a token:
git credential-osxkeychain erasethen type host=github.com, press Enter, and press Enter again on the blank line. Or find and delete the
entry in Keychain Access.
git config --global credential.helper managerGit Credential Manager ships with Git for Windows and is enabled by the installer. It stores credentials in Windows Credential Manager and handles browser-based and multi-factor authentication flows for the major hosting providers.
To clear a stored credential, open Credential Manager from Control Panel, choose Windows Credentials, and remove the entry for the host.
Why store is a poor choice
Section titled “Why store is a poor choice”git config --global credential.helper storeThis writes credentials to ~/.git-credentials in plain text:
https://username:ghp_examplenotarealtoken@github.comGit’s own documentation describes this helper as inherently insecure. The specific problems:
- Anything that can read your home directory can read your tokens. Other processes, backup software, synchronisation tools, a misconfigured container mount.
- The file is easy to include by accident in a backup, a support bundle, or a shared home directory.
- Nothing expires. A token written in 2023 is still there.
- There is no audit trail. Nothing records that the file was read.
If you have used store previously, the file persists after you change helpers. Remove it deliberately:
rm ~/.git-credentialsThen rotate anything that was in it, on the assumption that it may have been exposed.
Per-host configuration
Section titled “Per-host configuration”Different remotes often need different authentication. Credential settings can be scoped by URL:
[credential "https://github.com"] helper = manager username = your-username
[credential "https://git.internal.example.com"] helper = cache --timeout=7200By default Git treats all repositories on a host as sharing credentials —
https://example.com/team-a/repo and https://example.com/team-b/repo use the same entry. To distinguish
them:
git config --global credential.useHttpPath trueThis matters when you have different tokens for different projects on one host, and is worth knowing about because the default behaviour is occasionally surprising.
SSH: a different mechanism entirely
Section titled “SSH: a different mechanism entirely”SSH does not use the credential subsystem. Nothing on this page applies to it.
With SSH you generate a key pair, upload the public half to your hosting provider, and authenticate with the private half:
ssh-keygen -t ed25519 -C "you@example.com"That writes ~/.ssh/id_ed25519 (private — never share, never commit) and ~/.ssh/id_ed25519.pub
(public — safe to upload).
| HTTPS + credential helper | SSH keys | |
|---|---|---|
| Secret stored | Token, in a credential store | Private key file |
| Managed by | Git’s credential subsystem | ssh-agent and SSH config |
| Works behind restrictive firewalls | Usually | Sometimes blocked |
| Per-repository credentials | With useHttpPath | Per key, via SSH config |
| Rotation | Regenerate the token | Generate a new key pair |
| Passphrase protection | N/A | Yes — and you should use one |
Neither is universally better. HTTPS with a good helper is simpler to set up and works everywhere. SSH avoids token expiry and integrates with agent forwarding.
Git, the platform, and SSH
Section titled “Git, the platform, and SSH”Three layers get conflated here, and separating them makes troubleshooting much easier:
Git provides the credential subsystem: a protocol for asking helpers for credentials. It does not know what a token is, or which provider you are using.
The hosting platform decides what counts as a valid credential — a personal access token with particular scopes, an OAuth flow, an app installation token — and how long it lasts. Those rules are the platform’s, not Git’s.
SSH is a transport with its own authentication, entirely outside both.
So “Git authentication failed” is rarely a Git problem. It is usually an expired or insufficiently-scoped token, which is a platform matter.
Credentials in CI
Section titled “Credentials in CI”Automation authenticates differently from people, and the differences matter.
Use a dedicated credential. A CI job should not use a developer’s personal token. If that person leaves or rotates their token, the pipeline breaks; worse, the job inherits all of their access.
Prefer short-lived, scoped tokens. Most platforms can issue a token scoped to one repository for the duration of a single job. That is far better than a long-lived secret stored in configuration.
Never write credentials to a file in the workspace. A .git-credentials file inside a checked-out
repository is one careless git add away from being committed.
Pass tokens through the environment, and let the platform’s secret store hold them:
git clone "https://x-access-token:${CI_TOKEN}@github.com/org/repo.git"Rotate on any suspicion, and prefer platforms that let you scope tokens to a single repository and a single permission.
Troubleshooting
Section titled “Troubleshooting”Prompted on every operation. No helper is configured, or it cannot store anything:
git config --get credential.helperEmpty output means no helper.
Authentication fails after rotating a token. A stale credential is cached. Erase it:
printf 'protocol=https\nhost=github.com\n\n' | git credential rejectOr remove the entry through your platform’s credential store UI.
Which credential is Git using?
printf 'protocol=https\nhost=github.com\n\n' | git credential fillThis prints what the helpers supply — including the password field, so do not run it where the output could be captured or shared.
Trace the exchange:
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git fetch 2>&1 | head -40Verbose and useful, and it may include sensitive headers — review before pasting anywhere.
Permission denied over SSH:
ssh -T git@github.comMost providers respond with a message identifying you if the key is recognised.
Wrong account used. With several accounts on one host, either use credential.useHttpPath with HTTPS,
or distinct SSH hosts in ~/.ssh/config:
Host github-work HostName github.com IdentityFile ~/.ssh/id_ed25519_workThen clone with git@github-work:org/repo.git.
Choosing between HTTPS and SSH
Section titled “Choosing between HTTPS and SSH”Both are legitimate. The choice usually comes down to constraints rather than preference.
Choose HTTPS with a credential helper when:
- Your network blocks outbound SSH, which is common on corporate and guest networks.
- You want the platform’s authentication flow, including multi-factor and single sign-on.
- You work on many machines and prefer not to distribute private keys.
- Your organisation issues short-lived tokens centrally.
Choose SSH when:
- You dislike token expiry interrupting work.
- You already use SSH keys for servers and want one mechanism.
- You need agent forwarding.
- Your provider’s token lifetimes are shorter than your patience.
Use both where it makes sense: SSH for repositories you work in daily, HTTPS for CI and for one-off
clones. Nothing prevents different remotes using different protocols, and insteadOf rules can rewrite
one to the other per host:
[url "git@github.com:"] insteadOf = https://github.com/Security practices
Section titled “Security practices”Least privilege. Give a token only the scopes it needs. A token that can only read one repository is a far smaller problem if it leaks.
Short lifetimes. Prefer tokens that expire. Rotation stops being an event you have to remember.
Never commit credentials. If one is committed, rotate it immediately — removing the file or rewriting history does not un-leak it. See Editing Commit History.
Separate credentials for automation. CI should use its own credential with its own scopes, not a developer’s personal token.
Protect private keys with a passphrase.
Revoke on any suspicion. Revocation is cheap; investigating a compromise is not.
Common mistakes
Section titled “Common mistakes”Using store because it is the first result. Plain text on disk. Use cache or a keychain.
Leaving ~/.git-credentials behind after switching helpers. Delete it and rotate.
Committing a token. Rotate first, then clean up.
Assuming SSH uses credential helpers. Different mechanism entirely.
Unprotected SSH private keys.
Using one long-lived token everywhere. One leak compromises everything.
Disabling http.sslVerify to fix a certificate error. Configure the CA for that host instead.
Pasting git credential fill output into a bug report. It contains the password.
Mental Model
Section titled “Mental Model”Git is a doorman who does not keep keys.
When a door needs unlocking, it asks the keyholder — your credential helper — and passes along whatever it is given. It never inspects, validates or stores anything itself.
Which keyholder you appoint determines the security. A keychain keeps keys in a safe. The
storehelper keeps them on a sticky note by the door.
What You Learned
Section titled “What You Learned”- Git delegates authentication to credential helpers implementing
get,storeanderase. manager,osxkeychain,libsecretandwincreduse encrypted OS credential stores.cachekeeps credentials in memory with a timeout — the right choice with no keychain available.storewrites plain text to~/.git-credentialsand should be avoided.- Helpers can be configured per host;
credential.useHttpPathdistinguishes repositories on one host. - SSH authentication is a separate mechanism that does not use this subsystem.
git credential fillandrejectinspect and clear stored credentials.- Scope and lifetime limit damage; storage security alone does not.
Try It Yourself
Section titled “Try It Yourself”No real credentials required — this uses a local repository so nothing sensitive is involved.
-
Check what is configured:
Terminal window git config --get credential.helpergit config --show-origin --get credential.helper -
See the subsystem run. In a repository with an HTTPS remote:
Terminal window printf 'protocol=https\nhost=example.com\n\n' | git credential fillWith no helper and no stored credential, Git prompts. Press Ctrl+C to cancel.
-
Configure the memory cache:
Terminal window git config --global credential.helper 'cache --timeout=60' -
Confirm no file is created. After using it, check that
~/.git-credentialsdoes not exist:Terminal window ls -la ~/.git-credentials 2>&1 -
Inspect a per-host configuration:
Terminal window git config --global credential."https://example.com".username demo-usergit config --get-regexp '^credential\.' -
Practise clearing a credential:
Terminal window printf 'protocol=https\nhost=example.com\n\n' | git credential reject -
Clean up:
Terminal window git config --global --unset credential.helpergit config --global --remove-section 'credential.https://example.com'
Step 4 is the point of the exercise: cache genuinely never touches disk, which is what makes it the safe
fallback when no keychain exists.
Next Lesson
Section titled “Next Lesson”Credentials establish that you may push. Signing establishes who wrote a commit — a different question, with a carefully bounded answer.