Mutt was one of the first mail clients with serious OpenPGP support, and it remains one of the best. This guide covers getting signing and encryption working cleanly with a current GnuPG 2.x.
Two backends: GPGME vs classic¶
Mutt can talk to GnuPG in two ways:
| GPGME (recommended) | Classic (gpg.rc / pgp_*_command) |
|
|---|---|---|
| How | Links against the GPGME library and talks to gpg-agent directly |
Runs the gpg binary for each operation, using a set of pgp_*_command format strings |
| Config | A handful of crypt_* variables |
~15 command templates, normally from a sample gpg.rc |
| Passphrases | Always via gpg-agent + pinentry |
Historically piped in by Mutt (--passphrase-fd 0), which modern gpg-agent doesn't support by default |
| S/MIME | Also supported | Separate smime_* commands |
Old muttrcs from the GnuPG 1.x era usually source a copy of the sample gpg.rc, often with hard-coded paths like /usr/local/bin/gpg and a short key ID. With GnuPG 2.1+ those templates are mostly obsolete. Use GPGME unless your Mutt was built without it (mutt -v | grep GPGME).
Prerequisites: a key and a working agent¶
# Generate a key if you don't have one (ed25519/cv25519 by default on current GnuPG)
gpg --full-generate-key
# Show your keys with full fingerprints
gpg --list-secret-keys --keyid-format=long
Refer to your key by full fingerprint, not the 8-hex-digit "short ID" (0xD7F8…). Short IDs are trivially collided, and fake keys with matching short IDs exist on public keyservers.
Mutt is a terminal program, so gpg-agent needs to know which terminal to put the passphrase prompt on:
# ~/.bashrc / ~/.zshrc
export GPG_TTY=$(tty)
# ~/.gnupg/gpg-agent.conf
pinentry-program /usr/bin/pinentry-curses # or pinentry-tty, pinentry-gnome3, pinentry-mac
default-cache-ttl 1800 # cache the passphrase for 30 minutes
max-cache-ttl 7200
gpgconf --kill gpg-agent # restart the agent to pick up the changes
Test outside Mutt first. If this doesn't prompt and succeed, Mutt won't either:
echo test | gpg --clearsign
GPGME configuration¶
# ~/.config/mutt/gpg.muttrc
set crypt_use_gpgme = yes
# Which key to sign with: full fingerprint. Mutt ≥1.11 / NeoMutt name; older versions used $pgp_sign_as
set pgp_default_key = "0x1234567890ABCDEF1234567890ABCDEF12345678"
# Also encrypt to yourself, so you can read your own Sent folder
set pgp_self_encrypt = yes
# Behaviour
set crypt_autosign = no # sign everything? (many people prefer manual)
set crypt_replysign = yes # sign replies to signed messages
set crypt_replyencrypt = yes # encrypt replies to encrypted messages
set crypt_replysignencrypted = yes # sign replies to encrypted messages
set crypt_verify_sig = yes # verify signatures when displaying
set crypt_opportunistic_encrypt = yes # auto-enable encryption when keys for ALL recipients exist
set crypt_protected_headers_write = yes # encrypt the real Subject (Mutt ≥1.12)
set pgp_use_gpg_agent = yes # (implicit with GPGME; harmless)
# ~/.config/mutt/muttrc
source ~/.config/mutt/gpg.muttrc
For multiple accounts, set pgp_default_key in each per-account file. Otherwise mail from your work identity gets signed with your personal key (see Mutt, multiple accounts).
Using it¶
In the compose screen (after writing the message, before sending), press p for the PGP menu:
| Key | Action |
|---|---|
s |
Sign |
e |
Encrypt |
b |
Both sign and encrypt |
a |
Sign as… (choose a different key) |
c |
Clear (no crypto) |
m |
Toggle PGP/MIME vs inline |
O |
Toggle opportunistic encryption |
The compose screen's Security: line shows what will happen. Reading is automatic: signed messages show a verification banner, and encrypted ones trigger a pinentry prompt.
Useful extras:
- Attach your public key:
Esc kin the compose menu. - Import a key from a message:
Ctrl-Kin the index or pager. - Check a key's validity: in the key selection menu,
%shows details andcchecks signatures.
PGP/MIME vs inline¶
Always prefer PGP/MIME (RFC 3156), Mutt's default. Inline PGP (-----BEGIN PGP SIGNED MESSAGE----- in the body) breaks with attachments, non-ASCII text and HTML, and is only worth enabling (set pgp_autoinline) for a recipient whose client can't handle PGP/MIME.
Classic backend (gpg.rc)¶
If you must use the classic backend, don't hand-maintain a copied template. Source the sample your distribution ships with the same Mutt version, so it stays in sync:
# Find it
rpm -ql mutt | grep gpg.rc # Fedora/RHEL: usually /usr/share/doc/mutt/gpg.rc
dpkg -L mutt | grep gpg.rc # Debian/Ubuntu: /usr/share/doc/mutt/examples/gpg.rc
set crypt_use_gpgme = no
source /usr/share/doc/mutt/examples/gpg.rc # path from above
set pgp_default_key = "0x1234567890ABCDEF1234567890ABCDEF12345678"
set pgp_use_gpg_agent = yes
How the command templates work¶
Each pgp_*_command is a format string Mutt expands and runs. The % escapes:
| Escape | Expands to |
|---|---|
%f |
The temporary file holding the message or attachment to process |
%s |
The temporary file holding a detached signature (verification only) |
%p |
Empty if no passphrase is needed, otherwise PGPPASSFD=0. Used in conditionals like %?p?--passphrase-fd 0? |
%a |
The value of $pgp_sign_as / $pgp_default_key (used as %?a?-u %a?) |
%r |
One or more recipient key IDs |
Two lines in old templates deserve attention:
--passphrase-fd 0: GnuPG 2.1+ ignores passphrases piped this way unlessallow-loopback-pinentryand--pinentry-mode loopbackare configured. Withpgp_use_gpg_agent = yes, Mutt omits the passphrase and gpg-agent/pinentry handles it. This is the main reason old configs "suddenly" stop decrypting after an upgrade.--always-trustin the encrypt commands: it encrypts to whatever key matches the recipient, regardless of whether you've verified it. That's convenient, but it means a fake key for the recipient would be used silently. Consider removing it and managing trust deliberately (gpg --edit-key … lsign), or use TOFU (trust-model tofu+pgpingpg.conf).
The old pgp_good_sign trick, which pulls the localized "Good signature from" string out of GnuPG's message catalog with gettext, is still in the sample file. With GPGME none of this is needed.
Autocrypt¶
Mutt 1.13+ and NeoMutt support Autocrypt, which automatically exchanges keys in mail headers and enables encryption between Autocrypt-capable clients with no manual key management. It uses its own keyring separate from your main one:
set autocrypt = yes
set autocrypt_dir = "~/.config/mutt/autocrypt"
Run :autocrypt-acct-menu (or start Mutt once with autocrypt set) to create the account. It's opportunistic. It protects against passive eavesdropping and is easy to use, but it doesn't replace verified fingerprints for high-stakes correspondence.
Troubleshooting¶
| Symptom | Fix |
|---|---|
Could not decrypt PGP message / no prompt appears |
GPG_TTY isn't set in the shell that launched Mutt, or pinentry is a GUI program with no display. Set GPG_TTY and use pinentry-curses/pinentry-tty |
| Prompt garbles the Mutt screen | Use pinentry-tty, or press Ctrl-L to redraw after entering the passphrase |
gpg: signing failed: Inappropriate ioctl for device |
Classic backend without GPG_TTY; same fix |
| Signed with the wrong key | pgp_default_key is unset (GnuPG picked its default) or a multi-account hook didn't set it |
No (valid) keys found when encrypting |
Recipient's key isn't in your keyring, or it's expired or revoked. gpg --locate-keys [email protected] tries WKD and keyservers |
Recipients see noname attachments |
They're on a client without PGP/MIME support; send them unsigned or inline (m in the PGP menu) |
| Can't read your own sent encrypted mail | Enable pgp_self_encrypt (older: pgp_encrypt_self) |
Old config references /usr/local/bin/gpg or gpg-2comp |
Leftovers from GnuPG 1.x-era templates. Switch to GPGME or source your distro's current gpg.rc |
pgp_replysign, pgp_autosign, pgp_replyencrypt in old configs |
Deprecated synonyms for crypt_replysign, crypt_autosign, crypt_replyencrypt. Mutt still accepts them, but NeoMutt has dropped many old synonyms, so use the crypt_* names |