Getting Started

This page walks through the smallest possible end-to-end setup: install, configure one key backend, create an encrypted table, and confirm the data is actually encrypted at rest. It assumes the extension is already loaded — see Installation.

1. Pick a Key Backend

For a first try, the local wallet provider is the fastest path — no external service required. Production deployments typically use HashiCorp Vault / OpenBao or a PKCS#11 HSM; see Key Management Overview to choose.

# postgresql.conf
pg_vault_tde.kms_provider          = 'local'
pg_vault_tde.wallet_passphrase_env = 'TDE_WALLET_PASSPHRASE'
pg_vault_tde.wallet_auto_open      = on
pg_vault_tde.enabled               = on
export TDE_WALLET_PASSPHRASE='my-strong-wallet-passphrase'

Restart PostgreSQL, then initialize the wallet once (as superuser):

\set PASSPHRASE `echo $TDE_WALLET_PASSPHRASE`
SELECT pg_vault_tde_wallet_init(:'PASSPHRASE');
SELECT * FROM pg_vault_tde_wallet_status();

Full details, including HashiCorp Vault/OpenBao and PKCS#11/HSM setup, are in Key Management Overview.

2. Create an Encrypted Table

CREATE TABLE users (
    id     bigserial PRIMARY KEY,
    email  text,
    ssn    text,
    dob    date
) USING encrypted_heap;

INSERT INTO users (email, ssn, dob)
VALUES ('alice@example.com', '123-45-6789', '1990-01-15');

-- Data is transparently decrypted on read — no application changes needed
SELECT email, ssn FROM users WHERE id = 1;

The USING encrypted_heap clause is the only difference from a normal CREATE TABLE. Every column value in the table is encrypted with AES-256-GCM; regular heap tables elsewhere in the database are completely unaffected.

3. Add an Encrypted Index (Optional)

Equality lookups (=, IN, ON CONFLICT) can use a tde_btree index, which encrypts the index keys themselves with AES-256-SIV:

CREATE INDEX ON users USING tde_btree (email);

SELECT id FROM users WHERE email = 'alice@example.com';  -- uses the encrypted index

tde_btree does not support range predicates (>, <, BETWEEN) or index-only scans — see Encrypted Tables and Indexes for the full picture before deciding which columns to index this way.

4. Confirm It’s Actually Encrypted

-- Integrity/format scan over every tuple in the table
SELECT * FROM pg_vault_tde_verify_integrity('users');

-- Storage overhead added by encryption (37 bytes/tuple with the current wire format)
SELECT * FROM pg_vault_tde_encrypted_size('users');

For a byte-level check, stop PostgreSQL and grep the relation file on disk for a plaintext value you inserted (e.g. 123-45-6789) — it should not appear.

What Gets Encrypted

Layer Encrypted? Notes
Tuple user data ✅ Yes — AES-256-GCM Every column value in an encrypted_heap table
HeapTupleHeader (xmin/xmax/ctid/infomask) ✗ No Required in plaintext for MVCC
tde_btree index keys ✅ Yes (equality only) AES-256-SIV; opt-in per index
Standard btree/other index keys ✗ No Use tde_btree if the key values themselves are sensitive
TOAST (large column values) ✅ Yes Encrypted per-chunk, same DEK as the parent table
WAL ✅ Yes Tuple bytes are encrypted before heap_insert()
pg_statistic (ANALYZE output) ✗ No Plaintext — see Security Considerations

Only tables explicitly created (or altered) with encrypted_heap are affected — this is an opt-in, per-table access method, not a cluster-wide setting.

Next Steps