A Practical Guide to GPG Subkeys: Keeping Your Master Key Offline While Signing and Encrypting Daily
Most people who set up GPG do it the way gpg --full-generate-key nudges them to: one key, used for everything, sat in ~/.gnupg on the same laptop they take to the pub. That key signs your git commits, decrypts your email, and proves your identity to anyone who trusts your fingerprint. If that laptop is compromised, all of it is gone at once, and because the master key is what everyone else's trust signatures point at, you can't just quietly generate a new one. You have to get a revocation out, tell everyone who signed your key, and start the web of trust over again.
The fix has been in GPG since forever and almost nobody uses it: separate the key that proves who you are (the master, or "primary" key) from the keys that actually do work day to day (subkeys). The master key gets used exactly once in a while, offline, to certify subkeys and sign other people's keys. It never touches a machine connected to a network. The subkeys live on your laptop, get used constantly, and if one of them leaks you revoke it and mint a replacement without disturbing your identity or anyone's trust in it.
The plan
- Generate a master key with only the "certify" capability, no sign, no encrypt.
- Attach three subkeys to it: one for signing, one for encryption, one for authentication (handy for SSH).
- Generate a revocation certificate immediately, while you can.
- Back up the master secret key and the revocation certificate somewhere offline.
- Strip the master secret key out of your everyday keyring, leaving only the subkeys.
Everything below assumes a reasonably current GnuPG (2.2 or 2.4 series; check with gpg --version). Do this on a machine you trust, ideally one you're willing to disconnect from the network for the sensitive bits, and do it before you've built up years of signatures on an all-in-one key, because migrating an existing identity is a separate and more painful exercise than starting clean.
Step 1: generate a certify-only master key
The normal wizard doesn't let you pick capabilities, so use --expert:
gpg --expert --full-generate-key
Pick (8) RSA (set your own capabilities), or the ECC equivalent if you'd rather use Ed25519/Cv25519 (option 11 or similar depending on your GPG version, look for "ECC (set your own capabilities)"). GPG will show the current allowed actions, typically Sign, Certify, Encrypt. Toggle off everything except Certify by entering S then E at the prompt until only Certify remains, then Q to confirm.
For RSA, choose 4096 bits. For expiry, this is a judgement call: a long-lived offline key with a solid revocation certificate stashed safely is arguably fine with no expiry at all, since the risk you're managing is compromise, not staleness, and you can always set a shorter expiry later from the offline copy. Many people still set something like 2 years and extend it periodically as a forcing function to actually go and check on the offline backup. Either is defensible; just be deliberate about which one you're doing.
Fill in your name, email, and an optional comment as usual, then set a strong passphrase. Note the fingerprint GPG gives you at the end, you'll need it repeatedly:
gpg --list-secret-keys --keyid-format=long
Step 2: add the subkeys
Edit the key you just created:
gpg --expert --edit-key YOUR_KEY_ID
At the gpg> prompt, run addkey three times, once per capability:
- Signing subkey: choose RSA (sign only) or ECC (sign only), 4096 bits or your chosen curve, and a real expiry this time, e.g. 1 year. Signing subkeys should rotate; if one is ever compromised the blast radius is limited to signatures made after the leak and before the revocation.
- Encryption subkey: RSA (encrypt only) or ECC (encrypt only). Same reasoning on expiry.
- Authentication subkey: choose RSA/ECC with "set your own capabilities", toggle to Authenticate only. This one is what you'll use for SSH via
gpg-agent, which is a genuinely nice side effect of doing all this: one hardware-backed identity for signing, encrypting and logging into servers.
Run save when done. gpg -K should now show one sec line (the master) and three ssb lines underneath it, each tagged with its capability letter ([S], [E], [A]).
Step 3: generate the revocation certificate now, not later
Do this before anything can go wrong, not after:
gpg --output revoke-master.asc --gen-revoke YOUR_KEY_ID
This certificate lets anyone holding it revoke your master key even without the private key or passphrase, so it needs the same handling as the key itself: offline, backed up, not sitting in your home directory in plaintext indefinitely. If your master key is ever lost, stolen, or you simply forget the passphrase, this is the only way to tell the world to stop trusting it.
Step 4: back everything up, then take the master offline
Export both the full secret key and a subkeys-only version:
gpg --export-secret-keys --armor YOUR_KEY_ID > master-secret-full.asc
gpg --export-secret-subkeys --armor YOUR_KEY_ID > subkeys-secret-only.asc
gpg --export --armor YOUR_KEY_ID > public-key.asc
master-secret-full.asc, revoke-master.asc, and ideally a copy of your public key all belong on offline storage: an encrypted USB stick that lives in a drawer, not the machine you're reading this on. If you want something that survives magnetic media failure, paperkey extracts just the secret components (the public parts are recoverable from any keyserver) into a compact form you can print and store on paper as a last-resort backup.
Once that's safely elsewhere, remove the master secret key from your working keyring and reimport only the subkeys:
gpg --delete-secret-keys YOUR_KEY_ID
gpg --import subkeys-secret-only.asc
gpg --import public-key.asc
gpg -K now shows sec# for the master line, the # meaning the secret material isn't present, just a stub recording that the key exists and what it certified. The ssb subkey lines are unaffected and fully usable. Your laptop can sign, encrypt and authenticate exactly as before; it just can't certify new subkeys or sign anyone else's key, because that operation genuinely requires the offline master.
Using it day to day
Nothing about your daily workflow changes syntactically. gpg --encrypt, gpg --sign, git commit -S all keep working because GPG automatically picks the subkey with the matching capability. For git specifically, set the signing key to the master's fingerprint (not the subkey's):
git config --global user.signingkey YOUR_KEY_ID
git config --global commit.gpgsign true
Git resolves that ID to the key, GPG resolves the actual signing operation to whichever subkey currently has the [S] capability and hasn't expired, which is also exactly why rotating the signing subkey later doesn't require touching any git config.
For the authentication subkey to work as an SSH key, enable it in gpg-agent.conf:
echo "enable-ssh-support" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent
ssh-add -L # via gpg-agent, once GPG_TTY and SSH_AUTH_SOCK are wired up
Getting SSH_AUTH_SOCK pointed at gpg-agent's socket instead of a regular ssh-agent is the fiddly part and depends on your shell and window manager; the short version is exporting SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket) in your shell profile.
Rotating a subkey, or extending its expiry
This is the operation that requires the offline master, and it's the whole point of the exercise. Boot from or mount the offline backup, import the full secret key into a scratch GNUPGHOME so you don't pollute your daily one:
export GNUPGHOME=$(mktemp -d)
gpg --import master-secret-full.asc
gpg --edit-key YOUR_KEY_ID
From here, expire extends the master's own expiry if you set one, key N followed by expire extends an individual subkey, and addkey mints a fresh replacement subkey exactly as before. save, then export the updated public key and the new subkey secret material, carry those back to your normal machine, and import them. The old subkey secret material never has to leave the offline environment; only the newly signed public key material and, if you're rotating rather than extending, the new subkey's private half need to travel.
If a subkey is compromised rather than merely expiring, revoke it specifically rather than reaching for the master revocation certificate, which nukes the whole identity:
gpg --edit-key YOUR_KEY_ID
gpg> key 1
gpg> revkey
gpg> save
Going further: a hardware token
Once subkeys are separated out, moving them onto a smart card or a YubiKey is a natural next step: inside gpg --edit-key, selecting a subkey and running keytocard moves that subkey's private material onto the token permanently, GPG won't even let you export it again afterwards. At that point the private key never exists in memory on your laptop at all during a signing or decryption operation, only inside the token, which is a meaningfully stronger guarantee than "the file is encrypted on disk". It's a natural extension of the same offline-master idea, just pushed one layer further down: the master stays offline entirely, and now the subkeys are physically isolated from the machine that uses them, rather than merely logically separated in the keyring.
The one thing worth flagging honestly: none of this protects you from a compromised machine reading your plaintext after GPG has already decrypted it, or from a keylogger capturing your passphrase while a hardware token is plugged in and unlocked. Subkeys reduce the blast radius of a leaked private key and keep your long-term identity recoverable; they are not a substitute for a machine you actually trust at the moment you use them.