Postgres backups, restore-tested · checked
A Postgres backup that is restored and checked every time
Supabase's Free plan keeps no backups, Neon's Free plan keeps a 6-hour restore window, and each provider's own GitHub Actions recipe stops at pg_dump, without restoring anything. restoreproof backs your database up every week in your own GitHub Actions, restores the backup into a throwaway PostgreSQL of the same version, and compares every table, column, policy and row. When the backup would not restore, the job fails. Nothing is hosted by us, and nothing is sent to us. Until its repository is published, the commands below run the same test by hand.
Test a backup by hand
Set DATABASE_URL to the database's connection string. Run these where PostgreSQL's own programs are installed, of the server's version or newer; they need nothing from us and send nothing anywhere.
# 1. back up
pg_dump --format=custom --no-owner --no-privileges --file=backup.dump "$DATABASE_URL"
# 2. restore into a scratch database on a local PostgreSQL of the same major version
createdb restore_test
pg_restore --no-owner --no-privileges --exit-on-error --dbname=restore_test backup.dump
# 3. count every table on both sides, and compare
COUNT="SELECT format('SELECT %L, count(*) FROM %I.%I;', schemaname || '.' || relname, schemaname, relname) FROM pg_stat_user_tables ORDER BY 1"
psql "$DATABASE_URL" -XAtc "$COUNT" | psql "$DATABASE_URL" -XAt > live.txt
psql -d restore_test -XAtc "$COUNT" | psql -d restore_test -XAt > restored.txt
diff live.txt restored.txt && echo "every table came back with the same number of rows"--exit-on-error makes the restore stop at its first error instead of carrying on past it. A role that a row-level security policy names must exist first (createuser --no-login <role>), and so must every extension the backup creates (pgvector and PostGIS are packages of their own). The counts can differ by the rows written while the backup ran. This compares rows only; restoreproof also compares every column, constraint, index, function, trigger and policy.
What each run checks
- The backup restores:
pg_restore --exit-on-errorinto a throwaway PostgreSQL of the same major version, made on the runner withinitdb, on 127.0.0.1 with a random password. - Every table, column, constraint, index, view, function, trigger, row-level security policy, sequence, enum and domain of the source is in the copy, with the same definition.
- Every table's rows match the live count, taken in one READ ONLY snapshot just after the backup, give or take the rows written meanwhile (at most 10 or 10%). A table with rows that restores empty always fails.
- The database has not lost more than half its rows since the last good run, while last week's backup still holds them.
A recorded run
On a synthetic database shaped like a small Supabase project (auth and storage schemas, policies calling auth.uid(), a partitioned table), recorded 9 Oct 2026. Not anyone's data.
restoreproof 0.1.0 · restore test PASSED · 2026-10-09 20:37 UTC
source PostgreSQL 17.9 (supabase), 9.1 MB, read through RESTOREPROOF_DATABASE_URL
backup 91.5 KB in 0.1 s (pg_dump 17.9, custom format)
restored in 0.1 s into PostgreSQL 17.9, a throwaway cluster on 127.0.0.1 with no other connections
checked 5,925 rows in 9 tables against the live database: all match
92 schema objects: 11 tables, 37 columns, 17 constraints, 14 indexes, 1 view, 1
materialized view, 2 functions, 1 trigger, 3 policies, 3 sequences, 1 enum, 1 domain
table live → restored
auth.users 400 → 400
public.events_2026_09 1,500 → 1,500
public.events_2026_10 1,500 → 1,500
public.notes 2,000 → 2,000
public.profiles 400 → 400
public.settings 2 → 2
storage.buckets 1 → 1
storage.objects 120 → 120
supabase_migrations.schema_migrations 2 → 2
notes
- the rows of supabase_functions.hooks are left out of the backup by design, as platform data
Supabase writes for itself (its own restore guide leaves out the Storage vector tables); their
definitions are checked
- 1 Supabase platform schema(s) left out of the backup, as `supabase db dump` does: graphql
An email when a weekly restore test fails or does not run is not built.
It would email the people you name when a test fails, when the backup shrinks, or when no test has
run for eight days, from outside GitHub's scheduler. Today GitHub emails one person when a scheduled
run fails, and nobody when it never runs. agentcheck's free hourly watch covers MCP servers only. If
you would pay for this one, say so with one click:
https://agentwares-agentcheck.vercel.app/restoreproof?ref=restoreproof#watchBy provider
| Provider | What it keeps | Its own GitHub Actions recipe |
|---|---|---|
| Supabase | Free: none. Pro: 7 days of daily backups; Team 14; Enterprise 30. Never the files in Storage. | Dumps daily and commits the dump to your repository. No restore. |
| Neon | A restore window: 6 hours on Free, up to 7 days on Launch, up to 30 on Scale. | Dumps nightly to S3. No restore. |
Questions people search
- Supabase's Free plan has no backups
- Neon backup and restore: what Neon keeps, and a copy you keep
- How to test a Postgres backup
- Supabase pg_dump fails with "Network is unreachable" on GitHub Actions
An email when a weekly restore test fails or does not run is not built.
It would email the people you name when a weekly test fails, when the backup shrinks, or when no test has run for eight days, from outside GitHub's scheduler. Today GitHub emails one person when a scheduled run fails, and nobody when it never runs: it can drop a scheduled run under load, and it turns off a public repository's schedules after 60 days without activity. agentcheck's free hourly watch covers MCP servers only today. If you would pay for this one, say so with one click. The click is counted; nothing else is sent or stored.
Counted. Thank you; nothing else was sent.Other ways to keep a tested backup
As their own pages state them, read 9 October 2026. restoreproof differs in where it runs: in your own GitHub Actions, so the data goes nowhere but your artifacts and your bucket.
| Product | Price | Tests the restore | Where the data goes |
|---|---|---|---|
| Supabase point-in-time recovery | about $100 a month for 7 days, on a paid plan | no | Supabase |
| BackupDrill | free for 1 project weekly; $19 a month for 5 | yes | your bucket, through its workers; Supabase only |
| ReviveDB | free for 1 project weekly; €9 a month for 3 | yes | its own storage; Supabase only |
| SimpleBackups | free for 1 job; then $49, $99, $299 a month | no, restores are by hand | your storage, through its servers |
| Databasus | free, self-hosted | yes | a server you run |
Sources
- Supabase docs: Database backups (read 9 Oct 2026)
- Supabase docs: Automated backups using GitHub Actions (read 9 Oct 2026)
- Neon pricing (read 9 Oct 2026)
- Neon docs: Automate pg_dump backups to S3 with GitHub Actions (read 9 Oct 2026)
- GitHub docs: Downloading workflow artifacts (read 9 Oct 2026)
- GitHub docs: Notifications for workflow runs (read 9 Oct 2026)
restoreproof is a free tool from agentcheck, which runs scheduled checks of your endpoints and alerts you when an answer changes. Facts come from each provider's own pages, read on the day shown.