Backup & restore
Back up three things - Postgres, object storage and keys - encrypt them with keys you control, and rehearse the restore before you ever need it.
A backup is only as good as the last time you restored from it. This page covers what an OrthID deployment stores, how to capture it on a schedule, and a step-by-step restore you should run on a regular basis against a throwaway environment.
What to back up
An OrthID deployment has three pieces of durable state:
- Postgres. The system of record - users, organisations, sessions, RBAC, and the audit log. Protected by row-level security, so a logical dump preserves the tenant boundaries.
- Object storage. Larger blobs that do not live in Postgres: profile media, exported audit archives and signed document bundles.
- Keys. The signing and encryption key material. With BYOK this lives in your own KMS or HSM and you back it up there; without it, you must export the OrthID-managed keystore.
Back up Postgres
Use a logical dump for portability across versions. Run it against a read replica where possible so production write traffic is undisturbed.
# Logical, compressed dump of the OrthID database pg_dump \ --format=custom \ --no-owner \ --dbname="$ORTHID_DATABASE_URL" \ --file="orthid-$(date +%Y%m%d-%H%M).dump"
Encrypt with your own keys
OrthID never asks you to hand it a backup key. Encrypt the dump and the exported keystore with a key you control before either leaves the host, then ship the ciphertext to off-site storage in the same region as the deployment.
# Encrypt the dump to your team's public key, then upload age -r "$BACKUP_AGE_RECIPIENT" \ -o orthid-backup.dump.age orthid-20260622-0200.dump aws s3 cp orthid-backup.dump.age \ "s3://orthid-backups-au-syd-1/$(date +%Y/%m/%d)/" \ --sse aws:kms --sse-kms-key-id "$BACKUP_KMS_KEY"
Scheduled backups
Run backups as a Kubernetes CronJob in the deployment namespace. Keep a sensible retention window - for example nightly fulls held for 30 days, with a weekly copy held for a year for compliance.
apiVersion: batch/v1
kind: CronJob
metadata:
name: orthid-backup
namespace: orthid
spec:
schedule: "0 2 * * *" # 02:00 daily, in-region
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: backup
image: orthid/backup:2.7.0
args: ["dump", "--encrypt", "--upload"]
envFrom:
- secretRef:
name: orthid-backup-envRestore procedure
Restore is the part you must rehearse. The steps are deliberate so that a real incident is a checklist, not an improvisation:
- Provision a clean Postgres and an empty deployment in the region.
- Restore the keystore first, so decryption is available.
- Decrypt and restore the most recent good Postgres dump.
- Restore object storage to the matching bucket.
- Point OrthID at the restored database and start it.
- Verify: sign in, verify a token, read the audit log, and confirm decrypted fields render.
# Decrypt, then restore into a fresh database age -d -i backup-key.txt orthid-backup.dump.age > orthid-restore.dump pg_restore \ --no-owner \ --clean --if-exists \ --dbname="$ORTHID_RESTORE_URL" \ orthid-restore.dump