Encrypt Django Database and Media Backups
Encrypt PostgreSQL dumps and media backups for a Django app: use age or GPG for client-side encryption, restic or pgBackRest built-in encryption, server-side encryption with KMS on S3, manage and store keys separately, and test restores with decryption.
Problem statement
Your nightly pg_dump contains every user's email, password hash, address, and order history. It sits in an S3 bucket, maybe copied to a second provider and a laptop for testing. If any copy leaks, so does your entire database. Backups need encryption that protects them even if the storage provider, a bucket policy, or a credential fails, with keys stored somewhere other than next to the backups.
- Encrypt client-side before upload:
pg_dump -Fc | age -r <public-key> | aws s3 cp - s3://backups/.... - Or use a tool with built-in encryption: restic for files and dumps, pgBackRest or WAL-G for PostgreSQL with point-in-time recovery.
- Enable server-side encryption (SSE-KMS) on the bucket as an extra layer, not the only one.
- Store private keys outside the backup account: a password manager, a separate cloud account's secrets manager, and an offline copy.
- Test restores regularly, including decryption with the keys from where they are actually stored.
Step-by-step solution
1. Choose the encryption layer
- Client-side (age, GPG, restic, pgBackRest): data is encrypted on your server; the storage provider and anyone with bucket access see only ciphertext.
- Server-side (S3 SSE-S3 or SSE-KMS): the provider encrypts at rest; anyone with read access to the bucket and the KMS key gets plaintext.
Use client-side encryption for protection against leaked bucket credentials or misconfigured policies; add SSE-KMS for defense in depth and compliance.
2. Encrypt dumps with age
age uses public-key encryption, so the backup server only needs the public key and cannot decrypt old backups if compromised:
age-keygen -o backup-key.txt # do this on a secure machine; keep the private key offline
# public key looks like: age1qz...
pg_dump --format=custom --no-owner "$DATABASE_URL" \
| age -r age1qzexample... \
| aws s3 cp - "s3://shop-backups/pg/shop-$(date -u +%Y%m%dT%H%MZ).dump.age"
Restore:
aws s3 cp s3://shop-backups/pg/shop-20261003T0200Z.dump.age - \
| age -d -i backup-key.txt \
| pg_restore --no-owner --dbname "$RESTORE_DATABASE_URL"
3. Use built-in encryption in backup tools
restic encrypts and deduplicates everything in its repository with a password:
export RESTIC_REPOSITORY=s3:s3.amazonaws.com/shop-media-backups
export RESTIC_PASSWORD_FILE=/etc/restic/password
restic backup /srv/shop/media
restic forget --keep-daily 14 --keep-weekly 8 --prune
pgBackRest supports repo1-cipher-type=aes-256-cbc with a passphrase, and WAL-G supports libsodium or OpenPGP encryption, both covering base backups and WAL archives for point-in-time recovery.
4. Manage keys separately
The private key or passphrase must survive the loss of your servers and must not live in the same account as the backups:
- Store it in a password manager with restricted access, and in a secrets manager in a separate cloud account.
- Keep an offline copy (printed or on hardware) for disaster scenarios.
- Document who can access it and how to use it in the recovery runbook.
Losing the key means losing every backup encrypted with it.
5. Test decryption as part of restore tests
Schedule restore drills that fetch the key from where it is actually stored, decrypt, restore into a fresh database, and run Django checks against it. Automate a weekly restore of the latest dump into a scratch database and alert if it fails.
Explanation
Encryption at rest by the storage provider protects against stolen disks in the provider's data center, not against someone who obtains read access to the bucket. Client-side encryption makes the backup file itself unreadable without the key, so leaked credentials, public buckets, or copied files reveal nothing. Public-key schemes such as age go further: the server that creates backups cannot decrypt them, limiting damage if it is compromised.
Edge cases / notes
- Key rotation. Rotate keys periodically; keep old private keys as long as backups encrypted with them are retained.
- Compliance. Regulations such as GDPR treat encrypted backups more favorably in breach assessments, but retention and deletion obligations still apply.
- Performance. Encryption adds little CPU cost compared with compression and upload time.
Next steps
- Build the base backup with Back Up and Restore PostgreSQL for Django.
- Cover uploads with Back Up Django Media Files.
- Prove recovery with Test a Full PostgreSQL Restore for Django.
FAQ
Is S3 server-side encryption enough for backups?
It protects data at rest, but anyone with bucket and key access can read it. Add client-side encryption for stronger protection.
Should backups use public-key encryption?
It is a good choice: the backup server holds only the public key and cannot decrypt existing backups.
Where should I keep the decryption key?
Outside the backup account: a password manager, a separate secrets manager account, and an offline copy.
Do I need to encrypt media backups too?
Yes, if uploads contain personal or sensitive data. restic encrypts them by default.