Topic · Collaboration & Remotes

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.

SSH path — keypair git push your laptop ssh-agent signs with private key forge sshd checks public key HTTPS path — token git push your laptop credential helper supplies stored PAT forge https validates token

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

SSH keypair

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.

generate a new keyshell
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:

test SSH accessshell
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.

~/.ssh/configssh-config
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.

start the agent and add a keyshell
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.

Personal access token (PAT)

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:

  1. Visit Settings → Developer settings → Personal access tokens.
  2. 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 repo and are still needed for a few legacy endpoints.
  3. Set an expiry. Anything between 30 and 90 days is a reasonable default; GitHub auto-removes classic tokens after a year of disuse anyway.
  4. Copy the token the moment it appears — the forge will not show it again.
  5. 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
configure a helpershell
# 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"
Pitfall

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.

one-time forge loginshell
# 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.

Simplest setup

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.

opt in to SSH signingshell
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.

Agent forwarding is dangerous

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

Pitfall 1

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.

Pitfall 2

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.

Pitfall 3

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.

Pitfall 4

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
fresh-laptop setupshell
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
change remote URLshell
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.

first push under GCMshell
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
let the CLI do the workshell
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.

  • Managing your personal access tokens Reference GitHub Docs

    Distinction between fine-grained and classic PATs, scope choices, expiration policy, and the security guidance that drove the move away from passwords.

  • Pro Git, 7.14: Credential Storage Textbook Scott Chacon and Ben Straub

    The credential-helper concept explained from first principles, with the trade-offs between cache, store, and the platform keystores.

  • gitcredentials Reference Git Project · man page

    Authoritative description of the credential.* configuration namespace, helper resolution order, and per-URL overrides.

  • Git Credential Manager Reference git-ecosystem on GitHub

    The open-source cross-platform helper that handles OAuth to GitHub, GitLab, Bitbucket, and Azure DevOps. Install instructions and design notes live here.

  • About SSH Tutorial GitHub Docs

    High-level overview of how Git over SSH works against a forge — useful framing before the step-by-step setup pages.