Git, CI/CD & GitOpsXXXIII · Commit and Tag SigningSSHSigning
SSH signing setup — the Git 2.34+ approach using an existing SSH key
What you'll learn
- Switch the signing format from openpgp to ssh with gpg.format ssh
- Generate a dedicated SSH signing key and configure user.signingkey to its public key path
- Configure gpg.ssh.program when the system ssh-keygen is not on the default path
- Verify SSH-signed commits using gpg.ssh.allowedSigners
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
SSH signing is the modern alternative to GPG signing and the
right default for most production teams. Instead of
maintaining a separate GPG keyring in parallel with the
engineer’s existing SSH keys, the engineer reuses an SSH key
— typically the same key that authenticates git push — as
the signing key. One key per identity, one trust store, one
rotation ceremony. Git 2.34 (November 2021) added the
feature; GitHub rendered SSH-signed commits with the
“Verified” badge from March 2022.
Step 1 — Switch the signing format
git config --global gpg.format ssh
After this, every signing operation — git commit -S, git tag -s, and the implicit signing triggered by
commit.gpgsign and tag.gpgsign — uses SSH keys. The
existing GPG keyring is not touched.
flowchart TB
A["git commit"] --> B["gpg.format"]
B -->|openpgp| C["GPG signs"]
B -->|ssh| D["ssh-keygen signs"]
Step 2 — Generate (or reuse) a signing key
The simplest setup reuses the engineer’s existing push key.
The more disciplined setup generates a dedicated signing key
that is loaded into the SSH agent but not added to any
authorized_keys — signing is the only operation it can
perform.
# Reuse the existing push key
git config --global user.signingkey "$HOME/.ssh/id_ed25519.pub"
# Generate a dedicated signing key
ssh-keygen -t ed25519 -f "$HOME/.ssh/git_signing" \
-C "alice@corp.example.com signing key"
git config --global user.signingkey "$HOME/.ssh/git_signing.pub"
The dedicated-key discipline separates authentication (the push key, which can log into servers) from attestation (the signing key, which can only sign commits). A compromised push key does not produce forged signatures; a compromised signing key does not log into production.
The signing key must be loaded into the SSH agent:
ssh-add "$HOME/.ssh/git_signing"
ssh-add -l
# 256 SHA256:abc... alice@corp.example.com signing key (ED25519)
If the agent is not running or the key is not loaded, Git produces an unsigned commit with a warning, not a failure.
Step 3 — Configure the signing program
gpg.ssh.program names the program that produces the
signature. Default is ssh-keygen on PATH. Override only
when not at the default location:
git config --global gpg.ssh.program /usr/local/bin/ssh-keygen
The signature is in the format defined by the OpenSSH project
— the same format SSH certificates use. It is embedded in
the commit object as a header (gpgsig for GPG, ssig for
SSH).
Step 4 — Verify with an allowed signers file
The trust store for SSH signing is gpg.ssh.allowedSigners.
The file maps an email to a public key:
git config --global gpg.ssh.allowedSigners \
"$HOME/.config/git/allowed_signers"
cat >> "$HOME/.config/git/allowed_signers" <<'EOF'
alice@corp.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...rest of key...
bob@corp.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...rest of key...
EOF
Verification checks that the commit’s author email matches an entry and that the signature was produced by the listed key. The file must be version-controlled and reviewed, not maintained as a per-engineer local artefact.
Why SSH signing is the right default
Three properties: one key per identity (push and signing
keys can be the same, or siblings in the same agent), one
rotation ceremony (signing keys rotate on the push-key
schedule), no GPG toolchain on the workstation (uses
ssh-keygen and the SSH agent, both already present). The
cost is operational: the SSH agent must be running with the
key loaded.
Production discipline
- Use SSH signing for green-field setups. Shorter setup, lower operational burden.
- Generate a dedicated signing key. Isolates push-auth from signing-key failure modes.
- Version-control the
allowedSignersfile. Treat it as a security artefact reviewed via pull request. - Configure the SSH agent at login. Add the signing
key to
~/.ssh/configso the agent loads it on first use. - Verify in CI. A pipeline step that runs
git log --pretty=%G? <range>and fails onN,B, orE.
Cross-course references
- Git, CI/CD & GitOps — Part XXVI-06 (Signing
configuration) — the
gpg.format,user.signingkey, andcommit.gpgsignsettings. - Git, CI/CD & GitOps — Part XXXIII-02 (GPG signing setup) — the alternative setup for GPG teams.
- Linux for Production Sysadmins — Part XXXIV (ConfigMgmt) — the same key-rotation discipline for SSH keys used to authenticate to production hosts.
Quiz
Knowledge check · 4 questions
Q1. An engineer runs `git config --global gpg.format ssh` and `git config --global user.signingkey "$HOME/.ssh/id_ed25519.pub"`, then `git commit -m "fix"`. `commit.gpgsign` is unset. What does the resulting commit look like?
Q2. For SSH signing, the `gpg.ssh.allowedSigners` file is the analogue of a GPG trust database and maps email addresses to public keys.
Q3. Name the three commands to enable SSH signing, point at a signing key, and verify the configuration, and explain when `gpg.ssh.program` needs to be set.
Q4. Diagnose a silent signing failure where SSH-signed commits show as 'No signature' on the forge despite signing appearing enabled.
An engineer follows SSH-signing setup: `gpg.format ssh`, `user.signingkey ~/.ssh/id_ed25519.pub`, `commit.gpgsign true`. The engineer commits and pushes; GitHub shows 'Unverified'. Local `git log --show-signature` reports 'No signature'. The engineer has been in a tmux session running for weeks without restart.
Passing score: 75%. Answers are checked in this browser.