Git, CI/CD & GitOpsXXXIII · Commit and Tag SigningGPGSigning
GPG signing setup — generating a key, configuring Git, signing with -S
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
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
- Set an expiry on every key. A one- or two-year expiry forces the rotation conversation on a known schedule.
- Use the fingerprint, not the short key ID. The short ID has been forged; the fingerprint is collision-resistant.
- Turn
commit.gpgsignon at global scope. Signing is a default, not an opt-in. - 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.
- Migrate keys deliberately. Use
--export-secret-keysover an encrypted transport, or--export-secret-subkeys.
Cross-course references
- Git, CI/CD & GitOps — Part XXVI-06 (Signing
configuration) — the
user.signingkey,commit.gpgsign, andtag.gpgsignsettings. - 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
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?
Q2. `git commit --no-verify` produces an unsigned commit.
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.
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 <short-key-id>`, `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.