Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXIII · Commit and Tag SigningSSHSigning

SSH signing setup — the Git 2.34+ approach using an existing SSH key

Advanced⏱ ~22 mingitssh-keygen

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

Not yet marked complete on this device.

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

  1. Use SSH signing for green-field setups. Shorter setup, lower operational burden.
  2. Generate a dedicated signing key. Isolates push-auth from signing-key failure modes.
  3. Version-control the allowedSigners file. Treat it as a security artefact reviewed via pull request.
  4. Configure the SSH agent at login. Add the signing key to ~/.ssh/config so the agent loads it on first use.
  5. Verify in CI. A pipeline step that runs git log --pretty=%G? &lt;range&gt; and fails on N, B, or E.

Cross-course references

  • Git, CI/CD & GitOps — Part XXVI-06 (Signing configuration) — the gpg.format, user.signingkey, and commit.gpgsign settings.
  • 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

  1. 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?

  2. Q2. For SSH signing, the `gpg.ssh.allowedSigners` file is the analogue of a GPG trust database and maps email addresses to public keys.

  3. 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.

  4. 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.