← All guides

Encrypting Postgres backups properly (envelope encryption)

Bucket-level SSE is not enough for database dumps. Client-side AES-256-GCM with envelope encryption, explained with commands.

Why SSE alone is weak for backups

S3/R2 server-side encryption protects against someone stealing physical disks — not against anyone with bucket credentials, a misconfigured public bucket, or the provider itself. A database dump is your crown jewels; encrypt it before it leaves your process.

Simple: age or gpg in the pipe

pg_dump "$URL" -Fc | age -r age1yourpublickey... > db.dump.age
# restore:
age -d -i key.txt db.dump.age | pg_restore --no-owner -d target

Streaming, no plaintext on disk, tiny toolchain. Keep the private key OUTSIDE the bucket and test decryption quarterly.

Proper: envelope encryption

Real systems use two layers: each backup (or project) gets a random data key that encrypts the stream with AES-256-GCM (authenticated — tampering fails loudly at decrypt time), and that data key is stored wrapped by a master key living in a KMS or env-only secret. Rotating the master key means rewrapping tiny data keys, not re-encrypting terabytes. This is exactly Firedrill's scheme: per-project data keys, GCM streams, master key never stored beside the data.

A backup you've never restored is a hope, not a backup.

Firedrill restore-tests every backup it takes — on real infrastructure, with the report to prove it.