pass is a CLI password manager that stores each secret as an individual GPG-encrypted file inside a plain directory tree, and optionally versions that tree with git. It has no daemon, no proprietary database, and no vendor lock-in — it's built entirely from tools you probably already trust: GPG, git, find, and a shell script.
1. How it works (the model)¶
Understanding the model makes the "why is this secure" question answer itself.
- Your password store lives at
~/.password-store/by default. - Each secret is a separate file, named after its path, e.g.
~/.password-store/email/gmail.gpg. - Every
.gpgfile is the entire secret's content, GPG-encrypted to your public key. The first line is conventionally the password; subsequent lines can hold notes, TOTP seeds, usernames, etc. - Folder structure is just... folders.
pass lsmirrors~/.password-store/withtree. - Optionally,
~/.password-store/is a git repo, so every insert/edit/delete is a commit — you get history, diffs (of filenames, not contents, since contents are encrypted blobs), and can push to a remote for sync/backup. - There is no master password stored anywhere by
pass. Decryption is entirely delegated to GPG and your private key.
Because it's "just files," anything that works with files works with pass: syncing via git, backing up with rsync, browsing with find, scripting with bash, or reviewing changes with git log -p (filenames only, unless you enable content diffing, which you shouldn't).
2. Installing pass¶
# Debian/Ubuntu
sudo apt install pass
# Fedora/RHEL
sudo dnf install pass
# Arch
sudo pacman -S pass
# macOS
brew install pass
# WSL (Debian/Ubuntu-based)
sudo apt install pass gnupg2
You also need GPG (gnupg), which is usually already present or pulled in as a dependency.
3. Setting up GPG (the prerequisite)¶
pass doesn't do any cryptography itself — it shells out to GPG. So step one is having a GPG keypair.
3.1 Check for an existing key¶
gpg --list-secret-keys --keyid-format=long
If you already have a key you're happy to dedicate to this, you can skip to step 4. Otherwise, generate one specifically for pass (recommended — keep it separate from a key you use for signing commits or email, so store compromise doesn't cascade).
3.2 Generate a new key¶
gpg --full-generate-key
Recommended choices when prompted:
- Key type:
(9) ECC (sign and encrypt)if your GPG version supports it (modern, fast, Curve25519), or(1) RSA and RSAat 4096 bits if you want broader compatibility. - Expiration: set something, e.g.
2y. You can extend it later; an expiring key limits damage if you lose track of it and forget to revoke it. - Real name / email: doesn't need to be your real identity — it's just a label. Something like
Andrew (pass)and a throwaway-looking email like[email protected]is fine and makes the key easy to identify later. - Passphrase: this is the passphrase that ultimately protects every secret in your store. Make it strong and memorable — a long phrase beats a short complex string. This is the one passphrase you actually need to remember.
3.3 Find your key ID¶
gpg --list-secret-keys --keyid-format=long
Output looks like:
sec ed25519/A1B2C3D4E5F6A7B8 2026-08-04 [SC] [expires: 2028-08-04]
1234567890ABCDEF1234567890ABCDEF12345678
uid [ultimate] Andrew (pass) <pass@domain.tld>
The full 40-character fingerprint (second line) is what you want to use going forward — it's unambiguous, unlike the short ID.
3.4 Back up the key¶
This is the step people skip and regret. If you lose this key with no backup, every secret in your store becomes permanently unrecoverable — GPG has no backdoor by design.
# Export secret key (encrypted with your passphrase during export)
gpg --export-secret-keys --armor <fingerprint> > pass-key-private.asc
# Export public key too, for completeness
gpg --export --armor <fingerprint> > pass-key-public.asc
# Also generate a revocation certificate now, while you can
gpg --gen-revoke <fingerprint> > pass-key-revoke.asc
Store pass-key-private.asc and the revocation cert somewhere offline and durable — encrypted USB drive, printed paper in a safe, whatever your threat model calls for. Given your homelab setup, a good option is an encrypted volume on the QNAP that's not part of your usual container/backup sync path, plus a second offline copy.
4. Initializing the password store¶
pass init <fingerprint-or-email>
Example:
pass init [email protected]
This creates ~/.password-store/ and writes a .gpg-id file inside it containing the key ID(s) that new secrets should be encrypted to. Every future pass insert re-reads this file, so you can even encrypt to multiple recipients (e.g. yourself on two machines) by listing multiple IDs, one per line — useful if you want to decrypt the same store from more than one host, each with its own key.
4.1 Enable git tracking (strongly recommended)¶
cd ~/.password-store
git init
pass git config user.name "Andrew"
pass git config user.email "[email protected]"
From now on, pass insert, pass edit, pass rm, etc. auto-commit. You get a full audit trail of when secrets were added/changed/removed (not their contents).
To sync across machines, add a remote — ideally something private, e.g. a repo on your own git hosting or a private GitHub repo:
git remote add origin git@your-git-host:andrew/password-store.git
git push -u origin main
On another machine, git clone the repo into ~/.password-store, then pass init isn't needed again — the .gpg-id comes with the clone. You do need that machine's GPG to have your private key imported (gpg --import pass-key-private.asc), or add that machine's own key as an additional recipient.
5. Day-to-day usage¶
# List everything (tree view)
pass
pass ls
# Show a secret (prompts GPG passphrase, or uses gpg-agent cache)
pass show email/gmail
pass email/gmail # 'show' is the default action
# Copy password to clipboard (auto-clears after 45s), first line only
pass -c email/gmail
# Add a new secret interactively (multi-line)
pass insert email/gmail
# Add and auto-generate a strong random password
pass generate email/gmail 20
pass generate -n email/gmail 20 # no symbols
pass generate -c email/gmail 20 # generate + copy to clipboard, don't display
# Edit an existing entry in $EDITOR (decrypts, opens, re-encrypts on save)
pass edit email/gmail
# Move / rename
pass mv email/gmail personal/gmail
# Remove
pass rm email/gmail
# Search by filename
pass find gmail
# Search by content (decrypts everything, greps — slower, use sparingly)
pass grep "some-username"
# Git operations pass straight through
pass git log
pass git push
Organizing¶
Nesting is just directories:
pass insert work/sonova/vpn
pass insert work/sonova/azure-sp-clientsecret
pass insert homelab/opnsense-admin
pass insert homelab/qnap-admin
pass ls work shows just that subtree.
Multi-line entries¶
Convention: line 1 = password, everything after = free-form metadata.
Sup3rS3cr3tP@ss
username: andrew.mercer
url: https://vpn.sonova.com
totp: JBSWY3DPEHPK3PXP
notes: rotate quarterly
pass show work/sonova/vpn prints it all; pass -c work/sonova/vpn copies only line 1.
6. Useful extensions¶
- pass-otp: store and generate TOTP codes alongside the password (
pass otp email/gmail), so 2FA codes live in the same encrypted entry. - pass-update: bulk re-encrypt the store, e.g. after rotating your GPG key.
- browserpass / passff: browser extensions that autofill from your store, matching by domain.
- qtpass: a GUI on top of the same store, if you ever want one.
Install via your package manager (e.g. apt install pass-extension-otp) — they're just scripts dropped into ~/.password-store/.extensions/ or a system extensions dir, activated by setting PASSWORD_STORE_ENABLE_EXTENSIONS=true.
7. Why this is secure¶
a) Strong, well-vetted encryption, not a custom scheme.
Every secret is encrypted using GPG/OpenPGP — a battle-tested standard, not some bespoke crypto pass invented. The actual algorithm depends on your key type (e.g. AES-256 symmetric + your ECC/RSA keypair for the asymmetric layer), all implemented by GnuPG, one of the most heavily audited crypto tools in existence.
b) Per-secret encryption, not one big encrypted blob.
Password managers that use a single encrypted database (KeePass-style) decrypt everything into memory to read one entry, and a corrupted or bit-flipped file can threaten the whole vault. pass encrypts each secret independently — compromising one .gpg file exposes exactly one secret, and a corrupted file affects exactly one file.
c) The private key never has to touch disk in plaintext for use.
Your GPG private key is itself encrypted at rest with your passphrase. Decryption happens through gpg-agent, which caches your passphrase in memory for a configurable timeout so you're not retyping it constantly, but the passphrase itself is never written to disk. You can back this further with a hardware token (YubiKey with OpenPGP applet) so the private key material never leaves the device at all — GPG operations happen on-chip.
d) No plaintext secrets at rest, ever.
Unlike a .env file, a browser's saved-password store, or a spreadsheet, nothing in ~/.password-store/ is human-readable without a successful GPG decryption. Even the filenames (which reveal what secrets exist, e.g. homelab/opnsense-admin) are the only metadata leaked — the actual credentials are opaque ciphertext.
e) Minimal, auditable trust surface.
pass itself is a ~600-line POSIX shell script. You can read the entire thing in an afternoon (less $(command -v pass)). There's no closed-source binary, no telemetry, no cloud sync service you're trusting by default — the attack surface is GPG (widely audited) + git (widely audited) + a small shell script you can personally verify.
f) Git gives you tamper-evidence and recovery, without weakening confidentiality.
Because content is encrypted before it ever hits git, your git history/remote can be fully public in terms of transport security concerns — you're not leaking plaintext by pushing to a remote. You do still leak the structure (paths/filenames and commit timing), so avoid naming files in a way that itself discloses sensitive info (e.g. prefer work/vpn over work/vpn-password-is-hunter2).
g) Compromise containment.
Using a dedicated GPG key for pass (rather than reusing your email/signing key) means that if the pass key is ever compromised or needs rotating, it doesn't cascade into revoking your git-commit-signing identity or your email encryption — and vice versa.
h) No network dependency for decryption.
There's no central server that can be breached, rate-limited, subpoenaed, or go down. Decryption is 100% local, using GPG and a key you hold.
What pass does not protect against¶
Worth being clear-eyed about: it doesn't protect against a compromised endpoint with your gpg-agent unlocked (an attacker with an active session can read decrypted secrets same as you can), a keylogged passphrase, or a weak/short passphrase. It also doesn't hide which secrets you have, only their contents. For an attacker profile that includes device compromise while unlocked, pair it with a YubiKey (touch-to-decrypt) and a short gpg-agent cache timeout.
8. Quick reference¶
| Task | Command |
|---|---|
| Init store | pass init <gpg-id> |
| List secrets | pass / pass ls |
| Show secret | pass show path/to/secret |
| Copy to clipboard | pass -c path/to/secret |
| Insert manually | pass insert path/to/secret |
| Generate random | pass generate path/to/secret 24 |
| Edit | pass edit path/to/secret |
| Delete | pass rm path/to/secret |
| Rename/move | pass mv old new |
| Search filenames | pass find term |
| Search contents | pass grep term |
| Sync | pass git push / pass git pull |