Setting up GPG commit signing with subkeys Jump to heading
The default gpg --full-generate-key produces a single key that certifies, signs and often encrypts, and it lives on your laptop forever. That is the setup most people have, and it is the one that hurts most when the laptop is lost: the identity, every signature it made, and every trust relationship built on it go together. GPGβs subkey model fixes this. The primary key does nothing but certify subkeys and is kept somewhere safe; a signing subkey does the daily work and can be revoked and replaced without changing your identity. This page sets that up for Git. Whether you should use GPG at all is the subject of the parent topic, GPG vs SSH commit signing.
When to use this approach Jump to heading
- Your organisation or project already uses GPG β for release signing, package signing, or a published keyring.
- You need signatures that other tools verify through OpenPGP, such as distribution packaging.
- You want a long-lived identity whose signing capability can be rotated without changing the fingerprint people trust.
- If none of those apply, SSH commit signing is simpler and gives the same verification on forges.
Step 1 β Create a certify-only primary key Jump to heading
Generate the primary key with only the certify capability. It will never sign a commit; its job is to vouch for subkeys.
gpg --quick-generate-key "Alice Example <[email protected]>" ed25519 cert 5y
gpg --list-keys --keyid-format long [email protected]
# pub ed25519/0xA1B2C3D4E5F60718 2026-10-02 [C] [expires: 2031-10-01] The [C] flag confirms the key can only certify. Five years is a reasonable expiry for a primary key; you can extend it later as long as you still hold it, so expiry is a safety net, not a deadline.
Step 2 β Add a signing subkey with a shorter life Jump to heading
Add a subkey with only the sign capability and a shorter expiry. This is the key Git will actually use.
FPR=$(gpg --list-keys --with-colons [email protected] | awk -F: '/^fpr/{print $10; exit}')
gpg --quick-add-key "$FPR" ed25519 sign 1y
gpg --list-keys --keyid-format long [email protected]
# sub ed25519/0x1122334455667788 2026-10-02 [S] [expires: 2027-10-02] # Verification: there is exactly one usable signing subkey
gpg --list-keys --with-colons [email protected] | awk -F: '$1=="sub" && $12 ~ /s/' | wc -l Step 3 β Back up the primary key and remove it from the laptop Jump to heading
Export the full secret key, store it offline, and then delete the primaryβs secret part from the everyday keyring so only the subkey remains. The process is covered in depth in keeping a GPG primary key offline; the short version is below.
umask 077
gpg --export-secret-keys --armor "$FPR" > primary-and-subkeys.asc # to offline storage
gpg --gen-revoke "$FPR" > revoke-$FPR.asc # to offline storage
gpg --export-secret-subkeys --armor "$FPR" > subkeys-only.asc
gpg --delete-secret-keys "$FPR" # removes primary AND subkeys from this machine
gpg --import subkeys-only.asc # bring back only the subkeys
shred -u subkeys-only.asc β οΈ SAFETY WARNING:
gpg --delete-secret-keysis irreversible on this machine. Confirm the offline export imports correctly on another machine or in a temporaryGNUPGHOMEbefore deleting:GNUPGHOME=$(mktemp -d) gpg --import primary-and-subkeys.asc. If you delete first and the backup is bad, the identity is gone and every collaborator must re-establish trust in a new key.
# Verification: sec# means the primary secret is absent; ssb means the subkey is present
gpg --list-secret-keys --keyid-format long [email protected]
# sec# ed25519/0xA1B2C3D4E5F60718 ...
# ssb ed25519/0x1122334455667788 ... [S] Step 4 β Point Git at the subkey explicitly Jump to heading
Give Git the subkeyβs ID with a trailing !. Without the exclamation mark GPG picks whichever signing-capable key it prefers, which after a rotation may be the expired one.
git config --global gpg.format openpgp
git config --global user.signingKey 0x1122334455667788!
git config --global commit.gpgSign true
git config --global tag.gpgSign true Configure the agent so you are not asked for the passphrase on every commit, and so the prompt works in a terminal.
# ~/.gnupg/gpg-agent.conf
default-cache-ttl 3600
max-cache-ttl 28800 echo 'export GPG_TTY=$(tty)' >> ~/.profile
gpgconf --kill gpg-agent # Verification
git commit --allow-empty -m "gpg subkey test"
git log -1 --format='%G? %GK' # G 1122334455667788 Step 5 β Publish the public key to your forge Jump to heading
Export the public key β which includes the primary and all subkeys β and add it to your forge account. Forges match the subkey that made the signature to the primary you uploaded.
gpg --export --armor "$FPR" > alice.pub.asc
gh gpg-key add alice.pub.asc
gh api /user/gpg_keys --jq '.[] | {key_id, subkeys: [.subkeys[].key_id]}' When you later rotate the subkey, re-upload the public key so the forge learns about the new subkey. Forges do not fetch keys from keyservers on their own.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
How do I rotate the signing subkey when it expires? Jump to heading
Bring the primary key back temporarily β import it on an offline machine or into a temporary GNUPGHOME β add a new signing subkey, export the subkeys, import them on the laptop, update user.signingKey, and re-upload the public key. Old commits remain valid because the old subkey is still certified by the same primary.
Should I also create an encryption subkey? Jump to heading
Only if you need encrypted mail or files. Git signing never uses it. Keeping capabilities on separate subkeys is good hygiene, but there is no reason to create one you will not use.
Why does GPG still ask for my passphrase on every commit? Jump to heading
Usually because GPG_TTY is not set in the shell that runs Git, so the agent cannot prompt or cache properly, or because default-cache-ttl is zero. The fuller list of causes is in troubleshooting βgpg failed to sign the dataβ.
Related Jump to heading
- GPG vs SSH Commit Signing β the parent topic.
- Keeping a GPG Primary Key Offline β the custody side of this setup.
- Signing Git Commits with a YubiKey β moving the subkey onto hardware.
- Revoking a GPG Key and Publishing the Revocation β what the revocation certificate is for.