Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXVI · Git ConfigurationGitConfig

Signing configuration — keys, formats, and what production commits and tags should look like

Intermediate⏱ ~20 mingitgpgssh-keygen

What you'll learn

  • Configure user.signingkey to point at the key Git should use for signing
  • Distinguish the openpgp, ssh, and x509 signing formats and when each is the right choice
  • Use commit.gpgsign and tag.gpgsign to sign every commit and every tag by default
  • Recognise the production discipline of signing as a default, not an opt-in

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.

Git signing extends the chain of trust from the commit hash to a cryptographic identity. A signed commit carries a signature that proves the commit was produced by someone who holds a specific private key; a signed tag carries the same proof over a tag object. The configuration keys that make this work are a small set: a key reference, a format, two on-by-default toggles, and an optional program path for non-default signing backends.

The five keys that control signing

  • user.signingkey names the key Git should use. For gpg.format=openpgp, it is a key ID, a fingerprint, or an email address that GPG can resolve. For gpg.format=ssh, it is an SSH public key path (typically ~/.ssh/id_ed25519.pub).
  • gpg.format selects the signing backend. Three values: openpgp (default; uses GPG), ssh (uses an SSH key, the same key that authenticates pushes), x509 (uses an X.509 certificate and an external signing service like smimesign).
  • gpg.program (when format is openpgp) names the GPG binary. Defaults to gpg on the PATH.
  • gpg.ssh.program (when format is ssh) names the program that produces the SSH signature. Defaults to ssh-keygen.
  • commit.gpgsign and tag.gpgsign are boolean toggles that turn signing on by default for every commit and every tag in the current repository.
git config --global user.signingkey "$HOME/.ssh/id_ed25519.pub"
git config --global gpg.format ssh
git config --global gpg.ssh.program /usr/bin/ssh-keygen
git config --global commit.gpgsign true
git config --global tag.gpgsign true

After these commands, every commit and every tag the engineer makes will be signed with the SSH key ~/.ssh/id_ed25519. The signing is invisible - git commit produces a signed commit without any extra flag - because the toggles are on by default.

The three formats and when to use each

flowchart TB
    A["commit object"] --> B["gpg.format?"]
    B -->|openpgp| C["GPG signs with private key"]
    B -->|ssh| D["ssh-keygen signs with SSH key"]
    B -->|x509| E["smimesign signs with X.509 cert"]
    C --> F["signature embedded in commit"]
    D --> F
    E --> F
    F --> G["verifier checks key against trust store"]
  • openpgp is the default and the most widely deployed. GPG keys are well-supported by hosting providers (GitHub, GitLab, Bitbucket all render a green “Verified” badge for GPG-signed commits). The cost is key management: GPG keys expire, must be rotated, and live in a keyring that the engineer has to maintain.
  • ssh uses an SSH key as the signing key. The advantage is reuse: the same key that pushes the commit can sign it, so the engineer only manages one key per identity. GitHub has rendered SSH-signed commits with the “Verified” badge since 2022; GitLab supports them via a allowedSignersFile. The cost is the SSH agent must be running and have the key loaded for signing to work.
  • x509 uses an X.509 certificate and an external signing service (smimesign is the common one). The advantage is integration with enterprise PKI: the same certificate that authenticates the engineer to other systems can sign their commits. The cost is operational: certificates expire, the signing service must be reachable, and the engineer’s workstation must be configured to use it.

For most production teams, ssh is the right choice: the key the engineer already uses for git push is the key that signs their commits, no GPG keyring to maintain, and the verification story is well-supported.

Verifying signatures

Once commits are signed, the next question is verification:

git log --show-signature -1
git verify-commit HEAD
git verify-tag v1.2.3
git log --pretty='%h %an %G? %s'
# %G? prints "G" for a good signature, "B" for bad, "U" for good with unknown validity

--show-signature and --pretty=%G? are the diagnostics an auditor uses to confirm a commit was signed and the signature is valid. A “good with unknown validity” (U) result means the signature is correct but the signing key is not in the auditor’s trust store; the auditor must verify the key fingerprint out of band before accepting the signature.

For SSH signing, the trust store is the allowedSignersFile referenced by gpg.ssh.allowedSigners:

git config --global gpg.ssh.allowedSigners "$HOME/.config/git/allowed_signers"

The file maps an email to a key:

alice@corp.example.com ssh-ed25519 AAAA...rest of key...

Verification of an SSH-signed commit checks that the commit’s author email matches an entry in the allowed signers file and that the signature was produced by the listed key. The file is the SSH analogue of a GPG trust database.

Overrides and per-commit exceptions

Three mechanisms change signing for a single commit:

# Sign this commit with a specific key (override user.signingkey)
git commit -S -m "release cut"

# Skip signing for this commit only (the global commit.gpgsign is on)
git commit --no-gpg-sign -m "wip"

# Sign this commit with a format that differs from gpg.format
git -c gpg.format=openpgp -c user.signingkey=0xDEADBEEF commit -m "release"

--no-gpg-sign is the right escape hatch for “wip” commits on feature branches that never reach production. The escape hatch should be rare; a repository where every commit is --no-gpg-sign is a repository that has signing configured but not enforced.

Production discipline

  1. Choose one signing format and use it everywhere on the team. Mixed formats mean mixed verification stories; pick ssh or openpgp, not both.
  2. Set commit.gpgsign true and tag.gpgsign true at global scope. Signing is a default; per-commit opt-out (--no-gpg-sign) is the exception.
  3. Version-control the allowed signers file. For SSH signing, the allowedSignersFile is the trust database; it must be reviewable, not a per-engineer local file.
  4. Rotate keys on the same schedule as other credentials. A signing key that has been on a laptop that was lost is a signing key that must be removed from the allowed signers file and replaced.
  5. Verify signatures in CI. A pipeline step that runs git log --pretty=%G? and fails the build if any commit shows B or N (no signature) catches unsigned commits before they reach a signed release tag.

Cross-course references

  • Git, CI/CD & GitOps - Part XXX (SupplyChain) covers signed tags, signed artifacts, and the role of signed commits in the supply chain. The configuration in this lesson is what makes that supply chain verifiable.
  • Linux for Production Sysadmins - Part XII (RepoSecurity) covers apt/dnf repository signing; the same key-rotation and trust-database pattern applies.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer runs `git config --global commit.gpgsign true` and `git config --global tag.gpgsign true`, then commits a change with `git commit -m 'fix'`. What does the resulting commit look like?

  2. Q2. For SSH signing (`gpg.format ssh`), Git uses the key referenced by `user.signingkey` and verifies signatures against the file referenced by `gpg.ssh.allowedSigners`.

  3. Q3. Name the three signing formats `gpg.format` accepts and state one advantage and one cost of each.

  4. Q4. Recommend a signing configuration for a team migrating from unsigned commits to signed commits without breaking existing clones.

    A 10-engineer team has been committing without signing for years. The team wants to turn signing on for every commit and every tag going forward, but cannot rewrite the unsigned history (external consumers have cloned the repository). The team uses SSH keys for push and wants to avoid introducing GPG keys.

Passing score: 75%. Answers are checked in this browser.