Overview
A backup you have not restored is a hope. Disaster recovery means knowing what to protect, automating capture, storing copies outside the blast radius, and rehearsing a full restore before the real failure.
Set RPO and RTO before choosing tools
- Recovery Point Objective (RPO): the most data you can lose, as time. A one-hour RPO tolerates losing an hour of writes; a five-minute RPO needs continuous WAL archiving or PITR.
- Recovery Time Objective (RTO): the longest acceptable downtime. Four hours allows a restore from a daily
pg_dump; fifteen minutes needs a warm standby or fast PITR.
Write both in the runbook and verify them at every drill. A mismatch between targets and the real backup strategy is the most common failure.
Combine continuous and logical database backups
Use WAL-based point-in-time recovery for a short RPO and periodic logical pg_dump snapshots in an account you control. Supabase specifics (plan retention, PITR cost, what backups exclude): supabase-backups. For self-hosted Postgres, add pg_basebackup for faster large restores; see postgres-prod and postgres.
Back up secrets and configuration too
A restored database is useless without the config that connects the app to it. Keep secrets in a manager, not git (secrets-and-env), and confirm that:
- every secret has an owner and a documented rotation procedure,
- Terraform, Pulumi, or a config snapshot can recreate the environment without manual steps,
- API keys and OAuth credentials can be reissued without access to the primary account.
Store copies in a separate account and region
One compromised account or provider outage takes same-account backups with it. Use a separate cloud account (distinct login and billing), a different region from production, and immutable or versioned bucket policies so a stolen key cannot delete history. Provider-managed backups stay inside the provider; keep your own dumps elsewhere.
Alert when a backup does not run
A silent failure equals no backup. Ping a dead-man’s-switch service (Healthchecks.io or similar) only on success, and let it page when the ping stops:
0 2 * * * /usr/local/bin/run-backup.sh && curl -fsS "$HEALTHCHECK_PING_URL" >/dev/nullLog output and exit codes on each run.
Run a timed restore drill every quarter
- Pick a backup from the last 24 hours.
- Restore it to staging, never production.
- Run the application smoke tests against it.
- Measure the time from “incident declared” to “service verified” and the data gap, and compare them with RTO and RPO.
- Fix any gap before the next drill, and log the result.
Restoring secrets and config in the same drill proves the whole path. See hostinger-vps-backups for a VPS drill and incident-response for coordinating a real one.