pam_pg_sshkey is a PAM module that lets PostgreSQL authenticate database
users with the SSH keys they already have. The server stores public keys in
OpenSSH authorized_keys files; the client proves possession of the private
key by signing a one-time challenge. No password is stored on the server, in
the application, or in a connection string.
It is written in C against libpam and OpenSSL, ships a Python module for applications and replication clients, and is licensed under the MIT License. The current version is 1.1.0; see CHANGELOG.md.
- Nothing secret on the server. A compromised key tree reveals public keys only. There is no password hash to crack and no shared secret to rotate.
- Every token is single use. A captured token is refused on reuse and expires 60 seconds after it was made, so it is worthless to an observer.
- Works from anywhere, with nothing to set up per login. The client issues its own challenge; a laptop on the local socket and a replication subscriber in another region follow the same two steps.
- Uses what you already run. Keys come from
ssh-keygen, registration is anauthorized_keysfile, and any libpq client works:psqlthroughpg_sshkey_connect, psycopg2 throughpam_pg_sshkey.py, or your own code with a 13-byte prefix and one signature. - Proven, not promised. Every release runs the production module through libpam exactly as PostgreSQL does, and logs real OS users in against PostgreSQL 18 on Ubuntu and PostgreSQL 16 on Rocky Linux. See Testing.
The client issues its own challenge and signs it:
token = <unix_ts>:<nonce_hex>:<base64_signature>
message = "pg-sshkey-v2\0" || "<unix_ts>:<nonce_hex>"
It then connects with the token as the PostgreSQL password. PostgreSQL hands user and password to PAM, and the module accepts the login when the timestamp is within 60 seconds of server time, the signature verifies against one of the user's registered keys, and the nonce has not been seen before. The module records each nonce on first use, so a captured token cannot be replayed.
Nothing happens on the server before the connection. A client on the same host and a client on another continent follow the same steps, and the only operational requirement is that the two clocks agree to within 60 seconds.
On the database server:
make
sudo make install
sudo make install-confAdd a pam line to pg_hba.conf ahead of the default rules and reload:
host all all 0.0.0.0/0 pam pamservice=postgresql
Register a user's public key and create the role:
sudo pg_sshkey_addkey alice ~alice/.ssh/id_ed25519.pub
sudo -u postgres psql -c 'CREATE ROLE alice LOGIN'Connect as that user:
pg_sshkey_connect -h dbserver -U alice mydbpg_sshkey_connect computes the token and starts psql with it. Plain
psql cannot be used on its own: libpq must already hold the password when
PostgreSQL asks for it. The user guide explains this
and the Python equivalent.
| Where to start | Pick a page by task |
| Installation | Requirements, build, install, uninstall |
| Configuration | pg_hba.conf, the PAM service file, module options |
| User guide | Keys, connecting, the command-line tools |
| Python | pam_pg_sshkey.py for applications |
| Replication | Logical replication subscribers without stored passwords |
| Reference | Token formats, tool options, log messages |
| Security | Trust model, file permissions, hardening |
| Troubleshooting | Symptoms, causes, fixes |
| Testing | The test suite and the end-to-end matrix |
| Changelog | Notable changes |
src/
pam_pg_sshkey.c PAM module entry points
challenge_store.[ch] nonce records and sweeping
key_parser.[ch] authorized_keys to EVP_PKEY
sig_verify.[ch] Ed25519 and RSA verification
pg_sshkey_sign.c client: issue and sign a token
pg_sshkey_challenge.c server: create a v1 nonce (legacy)
pg_sshkey_connect client: token, then psql
pg_sshkey_addkey administrator: deploy a public key
pg_sshkey_query.py client: run one query with psycopg2
pam_pg_sshkey.py Python module
config/pam.d/postgresql PAM service file installed by make install-conf
tests/ unit, libpam-seam, system and end-to-end tests
docs/ documentation
logo/ logo and mark (SVG and PNG), see logo/README.md
badges/ README badges