PostgreSQL backups you can restore anywhere
backupd runs pg_dump against an exported snapshot on your schedule, then restores the backup into a sandbox and counts every table’s rows against your database.
Restore test
app-prod-2026-10-15-0300.pgdump.zst.age- 5 of 5
- 1,766,383
- 41s
- postgres 16
- Fetch the backup
- Decrypt
- Restore
- Count rows
- Clean up
| Table | Backed up | Restored | Result |
|---|---|---|---|
public.sessions | 1,402,377 | 1,402,377 | match |
public.order_items | 241,880 | 241,880 | match |
public.orders | 96,512 | 96,512 | match |
public.users | 18,204 | 18,204 | match |
public.invoices | 7,410 | 7,410 | match |
- 9.2 – 18
- PostgreSQL 9.2 through 18.
- pg_dump -Fc
- pg_dump’s custom format, from one snapshot.
- Restore-tested
- Restored and counted on our sandbox.
- Yours to open
- Open the file with age, zstd and pg_restore.
How a backup runs
- Dump from one snapshotWe open a read-only transaction, export its snapshot, and run pg_dump in custom format against it, counting rows inside the same snapshot.
- Compress and encrypt on the wayThe dump is compressed with zstd and encrypted with age as it streams to storage in 64 MiB parts. Nothing is written to a disk on the way.
- Check the file we storedThe stored file is read back, decrypted and decompressed, and both checksums are compared before the backup counts as good.
Backup
app-prod-2026-10-16-0900.pgdump.zst.age- Dump
- Compress
- Encrypt
- Upload
- Check
The restore test
On your plan’s cadence, the backup is restored into a temporary database on a sandbox Postgres server, never into yours.
Every table is counted and compared with the counts taken when the backup was made. If a table is short, you get an alert and the backup’s page names it. Then the temporary database is dropped.
What backupd needs
- A postgres:// or postgresql:// connection string.
- TLS is required: sslmode require, verify-ca or verify-full.
- A read-only user is enough. On a primary, backupd exports a snapshot so the dump and the row counts see the same moment.
- Your password is passed to the tools through the environment, never on the command line.
Add database
backupd connects over TLS. Use a read-only user.
Connection string
postgresql://backup:••••@db.example.com:5432/app
Schedule
Every hour
Keep backups
30 days
Restore it yourself
Every backup is encrypted with age to your key as well as ours. Download it, and open it with free tools on your own machine:
# PostgreSQL 17 and later
$ age -d -i backupd.agekey app.pgdump.zst.age | zstd -d \
| pg_restore --no-owner --single-transaction -d "$TARGET_URL"
# PostgreSQL 9.2 to 16
$ age -d -i backupd.agekey app.pgdump.zst.age | zstd -d \
| pg_restore --no-owner -f - | grep -v '^SET transaction_timeout' \
| psql --single-transaction -v ON_ERROR_STOP=1 "$TARGET_URL"Not yet
- Row counts are exact up to 25 million rows in total and when a snapshot can be exported, which needs a primary. Past that, or on a standby, they are estimates and those tables are not compared.
- Row counts cover the 1,000 largest tables. Smaller tables beyond that are backed up but not counted.
- Point-in-time recovery and incremental backups are not available yet; every backup is a full copy.
- Private networks and SSH tunnels are not supported yet.
Questions
Yes, as long as the database is reachable from the internet over TLS and the user you give us can run pg_dump.
We use pg_dump 18, which reads every server from 9.2 to 18. For servers before 17, restore through psql: the docs give the line.
We restore into a temporary database on our sandbox Postgres server, count every table with count(*), compare with the counts taken inside the same snapshot as the dump, then drop it.
Back up PostgreSQL tonight
Paste a connection string, pick a schedule and add your key. Starter is free for 14 days.