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/null

Log output and exit codes on each run.

Run a timed restore drill every quarter

  1. Pick a backup from the last 24 hours.
  2. Restore it to staging, never production.
  3. Run the application smoke tests against it.
  4. Measure the time from “incident declared” to “service verified” and the data gap, and compare them with RTO and RPO.
  5. 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.