Git, CI/CD & GitOpsXXXIV · Git SecurityAuthentication
Authentication options — SSH vs HTTPS and the trade-offs that decide
What you'll learn
- Compare the SSH and HTTPS authentication models for Git remotes
- Identify the operational trade-offs between key-based and token-based authentication
- Choose the right transport for a developer workstation, a shared CI runner, and a production deploy host
- Recognise the failure modes of each transport and the diagnostic command for each
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Git offers two transport-and-authentication stacks for remotes: SSH with public-key cryptography, and HTTPS with bearer tokens. Each is a complete stack — transport, authentication, and credential storage are bundled together — and the choice between them is one of the most consequential operational decisions a team makes. The right choice depends on where the credential is stored, how long it lives, and whether the client is a human or a machine.
SSH: key-based, agent-mediated
An SSH remote authenticates with a key pair. The private key
lives on the client (typically ~/.ssh/id_ed25519 or a
named key under ~/.ssh/), the public key is registered
with the forge, and authentication happens when the SSH
server challenges the client to prove possession of the
private key. The SSH agent holds the decrypted private key
in memory so the user does not re-enter the passphrase for
every push.
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
ssh-add ~/.ssh/id_ed25519
git clone git@github.com:acme/infrastructure.git
The advantages:
- No bearer token to leak. Authentication is a
cryptographic proof of key possession, not a string
passed in a URL. The credential cannot be exfiltrated by
an HTTP proxy log, an
~/.git-credentialsfile, or a CI log. - Fine-grained deploy keys. A per-repository public key with read-only or read/write scope (covered in XXXIV-02).
- Single sign-on across remotes. One SSH key registered with a forge works against every repository the account can see. No per-repository token rotation.
The disadvantages:
- Key management is operational. A key on a laptop is a credential that travels; a key in a CI runner is a credential that outlives the runner; a key without an expiry is a credential that cannot be rotated automatically.
- No per-repository scoping at the key level. A single SSH key registered against an account has the rights of the account. Per-repository restriction is done with deploy keys, not account-level keys.
HTTPS: token-based, helper-mediated
An HTTPS remote authenticates with a bearer token. The
token is passed in the URL or in the HTTP Authorization
header; the forge looks up the token, checks its scope, and
either accepts or refuses the request.
git clone https://x-access-token:TOKEN@github.com/acme/infrastructure.git
git clone https://USERNAME:TOKEN@gitlab.com/acme/infrastructure.git
The advantages:
- Per-repository scope at issuance time. A fine-grained personal access token (PAT) can be scoped to a single repository, a single permission (read, write, admin), and an explicit expiry. The token’s blast radius is its scope.
- Revocable from a UI without touching clients. A leaked token can be revoked from the forge’s settings page; clients fail the next time they try to use it.
- No key infrastructure required. A token is a string; it can be passed in an environment variable, stored in a secret manager, or scoped to a single CI job.
The disadvantages:
- The token is a bearer credential. Anyone with the token has the rights of the token. The token in a URL appears in shell history, in CI logs, in proxy logs, in backup snapshots.
- Per-repository tokens do not aggregate. A token for repository A is useless against repository B. A team with fifty repositories has fifty tokens, or a single account-scoped token with the rights of the account.
The decision matrix
flowchart LR
A["client"] --> B{"human or machine?"}
B -->|human workstation| C{"per-repo scoping?"}
B -->|machine / runner| D{"short-lived credential?"}
C -->|yes| E["HTTPS + fine-grained PAT"]
C -->|no| F["SSH + account key"]
D -->|yes| G["HTTPS + per-job token"]
D -->|no| H["SSH + deploy key with command restrictions"]
- Developer workstation, personal use: SSH. One account key registered against the engineer’s account, loaded into the agent, works against every repository the engineer can see. No tokens to manage.
- Developer workstation, scoped repositories: HTTPS + fine-grained PAT. The PAT is scoped to the repositories the engineer actually needs, expires on a schedule, and can be revoked from the forge.
- Shared CI runner, short-lived credentials: HTTPS +
per-job tokens (
GITHUB_TOKEN,GITLAB_TOKEN,CI_JOB_TOKEN). The token is issued per job, scoped to the job’s required permissions, and disappears when the job ends. - Production deploy host, long-lived: SSH + a deploy
key with
command=andfrom=restrictions (see XXXIV-02), or HTTPS + a deploy token with the narrowest scope.
The wrong default is whatever choice ignores the
credential-storage story. A team that picks SSH for machine
authentication but stores the private key in ~/.ssh/
without a passphrase on a shared runner has not chosen SSH;
it has chosen a plaintext credential on a shared host. A
team that picks HTTPS for human authentication but stores
the token in ~/.git-credentials (the store helper from
Part XXVI-05) has not chosen HTTPS; it has chosen a
plaintext credential in a backup-prone file.
Production discipline
- Default to SSH for human workstations, HTTPS for machine clients. The credential model matches the client model: long-lived keys for humans with agents; short-lived tokens for machines with secret managers.
- Never store an SSH private key without a passphrase on a shared host. A passphrase-protected key whose decrypted form lives only in the agent is a credential that requires the agent’s owner to use it.
- Never store an HTTPS token in
~/.git-credentialson anything that matters. Thestorehelper (Part XXVI-05) writes plaintext; the credential belongs in a secure-store helper or a per-job environment variable. - Audit the choice per remote, not per team. A team can run SSH for engineer workstations and HTTPS for CI without conflict; the choice is per remote, per client.
- Document the incident response for each transport. An SSH compromise is a key revocation in the forge and a key replacement on every client. An HTTPS compromise is a token revocation in the forge and a token replacement on every client. The two playbooks are different.
Cross-course references
- Git, CI/CD & GitOps — Part XXVI-05 (Credential helpers) — the helper layer that backs the HTTPS transport; the storage trade-offs apply directly.
- Git, CI/CD & GitOps — Part XX-01 (What a remote is) — the four URL schemes and how each handles authentication.
- Git, CI/CD & GitOps — Part XXVI-06 (Signing configuration) — SSH signing uses the SSH key pair that this lesson establishes.
- Linux for Production Sysadmins — Part XXXIV (ConfigMgmt) — the analogous SSH-vs-password decision for system administration.
Quiz
Knowledge check · 4 questions
Q1. A shared CI runner needs to push to a production remote. The runner persists no state between jobs. Which transport-and-credential model best fits the runner's lifetime model?
Q2. An SSH key registered against an account on a forge has, by default, the same set of rights as that account against every repository the account can see.
Q3. Name the two authentication models Git supports for remotes and the operational property that distinguishes them at the credential-storage layer.
Q4. Diagnose why a team's push is being rejected by the forge, and recommend the right transport change for each client.
A team standardised on HTTPS for all Git operations a year ago. Developers authenticate with a long-lived PAT stored in `~/.git-credentials` on their workstations; the CI runner uses a system-scope `credential.helper=store` with a long-lived PAT; the production deploy host uses a personal SSH key that an engineer copied from their laptop so they could 'just push the fix'. Last week the forge rotated the team's PAT as a precaution after a third-party breach; today every push from a developer workstation and every CI job fails with 'authentication required'; the production deploy host still works because its SSH key was never rotated.
Passing score: 75%. Answers are checked in this browser.