Credentials, SSH & Access
Every push, fetch, and clone against a private remote has to prove who you are. Two protocols dominate — SSH with keypairs and HTTPS with tokens — and behind them sits a small ecosystem of agents and credential helpers that decide how often Git pesters you for a password.
SSH vs HTTPS: the two paths
A remote URL begins with either git@ or https://, and that prefix decides which authentication path Git follows. Both reach the same repository on the same forge, but they prove your identity in very different ways.
SSH authenticates with a cryptographic keypair. You generate the keys once, paste the public half into your forge account, and from then on every push or fetch is silent — the private key proves who you are. The catch: SSH usually runs on TCP port 22, and corporate or hotel networks sometimes block it.
HTTPS authenticates with a username and a token, sent inside a normal TLS connection. It works on any network that allows web traffic, but Git will keep asking for the token unless a credential helper remembers it. Password-based HTTPS is no longer accepted on GitHub or GitLab; you must use a personal access token (PAT) or an OAuth flow through Git Credential Manager.
Both paths end with the same answer: the forge decides whether to accept the push. The interesting part is the middle box — the long-running helper that holds your secret so you do not have to type it on every operation.
SSH keys
A pair of cryptographically linked files: a private key that stays on your machine and a public key that you give to anyone who needs to verify you. The forge keeps the public key; signing with the private key proves the public key is yours.
Generate the keypair
GitHub and most modern guides recommend the Ed25519 algorithm. It is short, fast, and considered cryptographically strong. Use RSA only if a system you must talk to predates Ed25519 support — in that case go with at least 4096 bits.
ssh-keygen -t ed25519 -C "you@example.com"
# legacy fallback if the host does not understand Ed25519
ssh-keygen -t rsa -b 4096 -C "you@example.com"
The tool will ask where to save the key (accept the default ~/.ssh/id_ed25519) and offer a passphrase. Use a passphrase. It encrypts the private key at rest; the agent in the next section will decrypt it once per session so you do not have to retype.
Two files appear in ~/.ssh/:
id_ed25519— the private key. Never copy, paste, email, or commit this file.id_ed25519.pub— the public key. Open in a text editor, copy the whole single line, and paste it into the forge under Settings → SSH and GPG keys.
Test the connection
Before changing a remote URL, confirm the key works:
ssh -T git@github.com
# Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.
ssh -T git@gitlab.com
# Welcome to GitLab, @yourname!
The ~/.ssh/config file
If you connect to several forges, or you keep separate keys for work and personal accounts, an SSH config file removes the bookkeeping. Each Host block names an alias and tells SSH which key to use.
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
Now git clone git@github-work:acme/api.git uses the work key, and the default remote uses the personal one. IdentitiesOnly yes tells SSH not to try every key it knows; without it, the agent may offer the wrong key first and the forge will reject you before the right one is tried.
The ssh-agent
If your private key has a passphrase — and it should — you do not want to retype it for every push. The ssh-agent is a background process that holds decrypted keys in memory and answers signing requests on their behalf. You unlock the key once per session.
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# list keys currently loaded
ssh-add -l
On macOS, pass --apple-use-keychain to ssh-add so the passphrase is stored in the system keychain and the key is reloaded automatically after reboot. On Windows, the OpenSSH agent is a Windows service: start it with Start-Service ssh-agent in an elevated PowerShell. Password managers such as KeePassXC, 1Password, and Bitwarden can also act as agents, which lets you store the key inside the same vault that holds the rest of your secrets.
HTTPS and personal access tokens
If SSH is blocked, or you simply prefer to keep one set of network rules, HTTPS remotes still work — but the credential you supply is no longer your account password.
A long, random string issued by the forge that authenticates you in place of a password. PATs can be scoped to specific permissions and given an expiry date, which makes them safer than a reusable password and easier to revoke if leaked.
The shape of the workflow on GitHub:
- Visit Settings → Developer settings → Personal access tokens.
- Choose Fine-grained tokens when you can — you pick specific repositories and specific permission categories (Contents: Read & write, Pull requests: Read & write, and so on). Classic tokens grant broad scopes like
repoand are still needed for a few legacy endpoints. - Set an expiry. Anything between 30 and 90 days is a reasonable default; GitHub auto-removes classic tokens after a year of disuse anyway.
- Copy the token the moment it appears — the forge will not show it again.
- When Git prompts for a password during the next
push, paste the token. A credential helper will remember it so you do not have to paste again.
GitLab's flow is similar; the menu is at Preferences → Access tokens. Bitbucket calls them App passwords. The principles are identical: scope narrowly, expire often, store carefully.
Credential helpers
A credential helper is the piece of plumbing that answers when Git asks "what is the password for this URL?" Git ships with a generic protocol and several built-in helpers, plus a few platform-specific ones.
| Helper | Where it stores | When to use |
|---|---|---|
cache |
In-memory daemon, default 15 minutes | Shared machines where you accept retyping the token occasionally |
store |
Plain-text file at ~/.git-credentials |
Almost never — see warning below |
osxkeychain |
macOS Keychain | Default on macOS; encrypted, unlocked with the login password |
wincred |
Windows Credential Store | Built-in Windows option; superseded in practice by GCM |
libsecret |
Linux Secret Service (GNOME Keyring, KWallet) | Linux desktops with a running secret service |
manager (GCM) |
OS keystore plus OAuth flow | Modern default on Windows; recommended elsewhere too |
# short-lived in-memory cache (Linux/macOS)
git config --global credential.helper cache
# macOS keychain
git config --global credential.helper osxkeychain
# Git Credential Manager (cross-platform, after install)
git config --global credential.helper manager
# Custom cache timeout in seconds (here, four hours)
git config --global credential.helper "cache --timeout=14400"
Do not use credential.helper store. It writes your token to ~/.git-credentials in plaintext, readable by anyone who can read your home directory or any backup of it. Use the OS keystore (osxkeychain, wincred, libsecret) or Git Credential Manager instead. The only legitimate use for store is on a throwaway VM where you control the disk lifecycle.
Git Credential Manager
Git Credential Manager (GCM) is Microsoft's cross-platform helper, written in .NET and maintained in the open-source git-ecosystem/git-credential-manager repository. It runs on Windows, macOS, and Linux, and it speaks OAuth to the major forges — GitHub, GitLab, Bitbucket, Azure DevOps, and Azure DevOps Server — including the 2FA flow. On Windows it ships with Git for Windows; on macOS and Linux you install it separately. Once installed, set credential.helper to manager and the first push pops a browser window for OAuth instead of asking for a token.
Decision matrix
| Property | SSH keys | HTTPS + PAT | HTTPS + GCM (OAuth) |
|---|---|---|---|
| Setup effort | Generate key, upload to forge | Generate PAT, save in helper | Install GCM, sign in once per forge |
| Works behind strict firewalls | No — port 22 may be blocked | Yes — HTTPS/443 | Yes — HTTPS/443 |
| Rotation cost | Replace key on forge | Re-generate PAT every 30–90 days | Re-auth in browser, automatic refresh |
| 2FA / SSO friendliness | Independent of forge 2FA | PAT must be SSO-authorized per org | Native — SSO and 2FA handled in OAuth |
| Multi-account on one host | Easy via ~/.ssh/config |
Awkward — one token per URL prefix | Account chooser built in |
| CI / unattended use | Deploy key or machine user | PAT in CI secrets | Not designed for headless |
For a developer on a personal laptop, GCM with OAuth is the lowest-friction option today. For automation, SSH deploy keys or a scoped PAT remain the right tools. SSH is also the right choice if you want one credential model across many forges and self-hosted Git servers that may not speak OAuth.
Forge CLIs: gh and glab
GitHub and GitLab both ship official command-line clients that wrap the authentication setup. They run an OAuth flow, store the resulting token in your OS keystore, and configure Git's credential helper to use them. For most people this is the fastest path from a fresh machine to working pushes.
# GitHub
gh auth login
# GitLab
glab auth login
Both CLIs ask whether you want HTTPS or SSH, whether to authenticate via web browser or by pasting a token, and whether to use the CLI as Git's credential helper. Answer yes to that last question and you will not see another password prompt from git push.
If you are not sure what to pick, install the forge CLI and run gh auth login (or glab auth login). It wires up the token, configures the helper, and avoids every footgun in this document. You can switch to SSH later if you need it.
SSH signing of commits
Since Git 2.34 you can sign commits and tags with the same SSH key you already use for authentication. Before that, signing required a separate GPG keypair, and many people skipped it because they did not want to maintain a second key infrastructure. SSH signing removes that excuse.
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
The full mechanics — trust, allowed-signers files, forge verification — are covered in Signing, Trust & Provenance. The point here is that the same key you generated for SSH access can do double duty as your signing key, reducing the number of secrets you have to manage.
Security habits
- Passphrase every private key. Even if a laptop is stolen, the key remains encrypted at rest.
- Scope tokens minimally. A PAT for a deploy script should be able to read one repository and write nothing else. Fine-grained tokens make this easy on GitHub.
- Rotate after team changes. When someone leaves, revoke any shared deploy keys and rotate the project's PATs. Treat the moment as a forced expiry.
- Enable 2FA on every forge account. A stolen password without 2FA is enough to push malicious commits in your name.
- Audit "Authorized OAuth apps" twice a year. Old integrations accumulate. Remove anything you do not actively use.
- Never commit a secret. If you do, rotate the secret first — pulling it from history with history rewriting is repair, not removal. The leaked value must be considered burned.
Agent forwarding
The -A flag (or ForwardAgent yes in ~/.ssh/config) lets a remote server you SSH into reach back through your local agent to use your keys for outgoing connections. It is convenient when you want to git clone on a build server using your own credentials.
While you are connected with agent forwarding, anyone with root on the remote server can ask your agent to sign requests — including authenticating as you to other servers. Never forward your agent to a machine you do not fully trust. Prefer deploy keys on the server, or ProxyJump through a trusted bastion, which keeps signing local to your laptop.
Deploy keys
A deploy key is an SSH key registered against a single repository, not a user. Generate a keypair on the server that needs read-only access (a CI runner, a production host pulling releases), upload the public half under the repository's Deploy keys settings, and that server can clone or fetch but cannot push to other repositories — even if the key is later stolen. Treat deploy keys as the per-machine, per-repo equivalent of a finely scoped PAT.
Common pitfalls
Cached HTTPS credentials outlive their token. You rotate a PAT, but Git keeps pushing with the old one from the keychain until the helper finally errors out. Run git credential reject or clear the entry from the OS keystore by hand after rotation.
Wrong key offered first. Without IdentitiesOnly yes in ~/.ssh/config, the agent may try every loaded key in order. After a few rejections the forge bans your IP for a minute. Pin the identity per host.
SSO blocks an apparently valid PAT. Organizations with SAML SSO require you to authorize each token for each org. The token works on personal repos but 403s on the company's. Visit the token's settings page and click Authorize for the org.
Mixing remote URL types. If origin is HTTPS but you generated SSH keys, you will keep hitting the password prompt. Run git remote set-url origin git@github.com:user/repo.git to switch, or vice versa.
Worked examples
Example 1: Set up SSH access on a fresh laptop
ssh-keygen -t ed25519 -C "you@example.com"
# Enter file: (default)
# Enter passphrase: ********
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# copy the public key to clipboard (macOS)
pbcopy < ~/.ssh/id_ed25519.pub
# paste into GitHub: Settings -> SSH and GPG keys -> New SSH key
ssh -T git@github.com
# Hi yourname! You've successfully authenticated...
From here, any clone with a git@github.com: URL will succeed without further prompts for the rest of the session.
Example 2: Switch an existing repository from HTTPS to SSH
git remote -v
# origin https://github.com/user/project.git (fetch)
# origin https://github.com/user/project.git (push)
git remote set-url origin git@github.com:user/project.git
git remote -v
# origin git@github.com:user/project.git (fetch)
# origin git@github.com:user/project.git (push)
git fetch origin
The repository content does not change; only the protocol Git uses to talk to origin does.
Example 3: Issue a fine-grained PAT and use it via GCM
In the GitHub UI: Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token. Pick the repository, give it Contents: Read & write and Pull requests: Read & write, set a 90-day expiry, and copy the token.
git config --global credential.helper manager
git push origin main
# Username for 'https://github.com': yourname
# Password for 'https://yourname@github.com': <paste token>
# To https://github.com/user/project.git
# a1b2c3d..e4f5g6h main -> main
The next push will not prompt. GCM stored the token in the OS keystore and will replay it on every HTTPS request to that host.
Example 4: One-shot OAuth setup with gh auth login
gh auth login
# ? What account do you want to log into? GitHub.com
# ? What is your preferred protocol for Git operations? HTTPS
# ? Authenticate Git with your GitHub credentials? Yes
# ? How would you like to authenticate GitHub CLI? Login with a web browser
# ! First copy your one-time code: ABCD-1234
# Press Enter to open github.com in your browser...
# ... browser flow ...
# ✓ Authentication complete.
# ✓ Configured git protocol
# ✓ Logged in as yourname
git push # silently succeeds
No PAT to copy by hand, no helper to configure manually. The CLI registered itself as the credential helper and stored the OAuth token in your keystore.
Sources & further reading
-
The canonical step-by-step for
ssh-keygen -t ed25519, agent setup per platform, and the macOS keychain integration. -
Distinction between fine-grained and classic PATs, scope choices, expiration policy, and the security guidance that drove the move away from passwords.
-
The credential-helper concept explained from first principles, with the trade-offs between
cache,store, and the platform keystores. -
Authoritative description of the
credential.*configuration namespace, helper resolution order, and per-URL overrides. -
The open-source cross-platform helper that handles OAuth to GitHub, GitLab, Bitbucket, and Azure DevOps. Install instructions and design notes live here.
-
High-level overview of how Git over SSH works against a forge — useful framing before the step-by-step setup pages.