Signing Git commits with GPG
Published: March 24, 2026How I set up GPG to sign Git commits for GitHub, from key generation through to the everyday workflow with subkeys.
I wanted to sign my Git commits so they show up as verified on GitHub. GPG was the standard way to do this at the time. It turned out to be more involved than expected — generating keys, managing subkeys, configuring Git, and dealing with expiration renewals. I've since switched to SSH signing which is far simpler, but this walkthrough captures the full GPG process in case I ever need it again.
Generating a key pair
GPG 2.1.17+ generates RSA 4096-bit keys by default:
gpg --full-generate-key
This creates a primary key (for certification and signing) and may create an encryption subkey. The primary key is the one you want to protect — it's your identity.
Understanding the key listing
After generating, list your keys:
gpg --list-secret-keys --keyid-format long
The output shows abbreviations that aren't obvious at first:
sec— your secret (primary) keyssb— secret subkeysec#— the primary secret key has been removed (only subkeys remain)S,C,E,A— signing, certification, encryption, authentication capabilities
The long key ID after the algorithm (e.g. rsa4096/ABC123DEF456) is what you'll use for Git config and GitHub.
Configuring Git
Tell Git to use your signing key:
git config --global user.signingkey ${KEYID}
git config --global commit.gpgsign true
Adding the key to GitHub
Export your public key and paste it into GitHub under Settings → SSH and GPG keys:
gpg --export --armor ${KEYID}
Protecting the primary key with subkeys
The safest setup is to create a signing subkey, export it separately, then remove the primary secret key from your everyday keyring. This way, if your machine is compromised, the attacker gets the subkey but not the primary key — you can revoke the subkey and create a new one.
Export everything for backup first:
gpg --output ${KEYID}-private.asc --export-secret-keys --armor ${KEYID}
gpg --output ${KEYID}-public.asc --export --armor ${KEYID}
Export subkeys only:
gpg --output ${KEYID}-subkeys.asc --export-secret-subkeys --armor ${KEYID}
Now delete all keys and re-import only the subkeys:
gpg --delete-secret-and-public-keys ${KEYID}
gpg --import ${KEYID}-public.asc
gpg --import ${KEYID}-subkeys.asc
Running gpg --list-secret-keys should now show sec# — the primary key is gone from this machine. Store the full
backup somewhere safe and offline.
After importing, the trust level resets. Set it back to ultimate:
gpg --edit-key ${KEYID}
gpg> trust
Your decision? 5
gpg> quit
Renewing key expiration
Keys should have an expiration date. When they're about to expire, you need the primary secret key to extend them. This means temporarily importing the full backup.
Import the full backup:
gpg --import ${KEYID}-private.asc
Edit the key and set new expiration on the primary key:
gpg --edit-key ${KEYID}
gpg> expire
Key is valid for? (0) 1y
Then select all subkeys and update them too:
gpg> key 1
gpg> key 2
gpg> expire
Key is valid for? (0) 1y
gpg> save
Verify the new dates:
gpg --list-secret-keys
Now repeat the protection process — back up everything, delete all keys, re-import only subkeys, and set trust again. This is the annoying part. Every renewal cycle requires this dance.
Why I switched to SSH signing
Git 2.34+ supports signing commits with SSH keys. The setup is two commands and there's no key management ceremony:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
No expiration renewals, no subkey juggling, no importing and deleting backups. GitHub supports it. If a key is compromised, you just generate a new one and swap it out in GitHub. You can also rotate SSH keys on a regular basis without any of the ceremony GPG requires. If you're starting fresh, use SSH signing.