Skip to main content
RunBook Academy

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

GPG signing setup — generating a key, configuring Git, signing with -S

Advanced⏱ ~24 mingitgpg

What you'll learn

  • Generate a GPG key suitable for commit signing and identify the key ID and fingerprint
  • Configure user.signingkey so Git selects the right key without per-command flags
  • Sign a commit with git commit -S and explain the role of -S relative to commit.gpgsign
  • Distinguish --no-verify (skip hooks) from --no-gpg-sign (skip signing) and use each safely

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.

GPG signing is the historical default for commit signing and the format most widely supported by forges. The setup is a sequence of three steps: generate the keypair, configure Git to use the key, and produce a signed commit with the -S flag. Each step has a footgun worth knowing — the key generation defaults, the way Git picks the key, and the relationship between -S and --no-verify.

Step 1 — Generate a GPG key

The tool is gpg (or gpg2 on some distributions). The default algorithm is RSA 3072 bits with a non-expiring key — the latter is the most common footgun. A non-expiring key cannot be rotated cleanly; set an expiry date during generation and rotate on schedule.

gpg --gen-key
# prompts: kind of key (RSA, default), key size (3072, default),
# expiry (recommend 1-2 years, not never), name, email, passphrase

The interactive prompts produce a keypair in ~/.gnupg. The passphrase encrypts the private key at rest; an empty passphrase is a key any process running as the user can use.

gpg --list-secret-keys --keyid-format=long
# sec   ed25519 2024-01-15 [SC]
#       ABCDEF1234567890ABCDEF1234567890ABCDEF12
# uid           [ultimate] Alice Engineer <alice@corp.example.com>

KEY_ID="ABCDEF1234567890ABCDEF1234567890ABCDEF12"

gpg --armor --export "$KEY_ID"
# -----BEGIN PGP PUBLIC KEY BLOCK-----
# mDMEZ...
# -----END PGP PUBLIC KEY BLOCK-----

The 40-character hex string is the fingerprint — the stable identifier across systems. The short key ID (last 16 hex digits) is for everyday use but is collision-prone. The ASCII-armoured block is what the forge ingests when the engineer adds a GPG key to their account.

Step 2 — Configure Git to use the key

git config --global user.signingkey "$KEY_ID"
git config --global user.signingkey "alice@corp.example.com"

The format selector defaults to openpgp. With commit.gpgsign true toggled, every git commit produces a signed commit without any extra flag. The -S flag is the explicit form for the rare case where signing is off by default.

flowchart LR
    A["git commit"] --> B{"commit.gpgsign?"}
    B -->|true| C["sign with user.signingkey"]
    B -->|false| D{"-S?"}
    D -->|yes| E["sign with -S"]
    D -->|no| F["unsigned"]

The discipline from Part XXVI-06 is to set commit.gpgsign true at global scope so the -S flag is the exception. Engineers who must remember -S will eventually forget.

Step 3 — Sign a commit

git commit -S -m "fix: typo in production deploy script"

git commit -S -u "$KEY_ID" \
    -m "release: cut v3.2.7 from release branch"

The flag is capital -S because lowercase -s suppresses the diff stat from commit output. A typo (-s instead of -S) is an unsigned commit that looks successful.

git verify-commit HEAD

git log --pretty='%h %an %G? %s' -5
# 8a3f9d2 Alice Engineer G fix: typo
# 1c2d3e4 Alice Engineer N docs: update README

%G? prints G (good), B (bad), U (good, unknown validity), X/Y/R (expired/revoked), E (cannot check), or N (no signature).

Signing and hooks — --no-verify versus --no-gpg-sign

The two flags are easy to confuse. --no-verify skips the pre-commit and commit-msg hooks; the commit is still signed. --no-gpg-sign signs the commit as unsigned; the hooks still run.

git commit --no-verify -S -m "hotfix: bypass pre-commit linter"

git commit --no-gpg-sign -m "wip: scratch notes"

A production commit should never use --no-gpg-sign; a production commit should rarely use --no-verify.

Production discipline

  1. Set an expiry on every key. A one- or two-year expiry forces the rotation conversation on a known schedule.
  2. Use the fingerprint, not the short key ID. The short ID has been forged; the fingerprint is collision-resistant.
  3. Turn commit.gpgsign on at global scope. Signing is a default, not an opt-in.
  4. Upload the public key to the forge before pushing. A signed commit whose signing key is not on the forge shows as “Unverified” in the UI.
  5. Migrate keys deliberately. Use --export-secret-keys over an encrypted transport, or --export-secret-subkeys.

Cross-course references

  • Git, CI/CD & GitOps — Part XXVI-06 (Signing configuration) — the user.signingkey, commit.gpgsign, and tag.gpgsign settings.
  • Git, CI/CD & GitOps — Part XXXIII-03 (SSH signing setup) — the SSH-key alternative.
  • Linux for Production Sysadmins — Part XII (RepositorySecurity) — the same key-rotation discipline for apt/dnf repository signing keys.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer runs `git config --global user.signingkey "ABCDEF1234567890"` then `git commit -m "fix: typo"`. `commit.gpgsign` is unset. What does the resulting commit look like?

  2. Q2. `git commit --no-verify` produces an unsigned commit.

  3. Q3. Name the three commands to generate a GPG key, configure Git to use it, and produce a signed commit, and identify the configuration key that links the second and third steps.

  4. Q4. Diagnose a signing failure where commits show as 'Unverified' on the forge despite passing local cryptographic check.

    An engineer sets up GPG signing: `gpg --gen-key`, `git config --global user.signingkey &lt;short-key-id&gt;`, `git config --global commit.gpgsign true`. Local `git log --show-signature` shows 'good signature'. The engineer pushes to GitHub; every commit shows 'Unverified'. The CI verifier rejects the commits.

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