$ dbaguide.com / cheat-sheets / postgresql-backup-recovery

PostgreSQL Backup & Recovery Reference

Production guide to pg_dump, pg_basebackup, WAL archiving, pgBackRest, PITR and disaster scenarios

PostgreSQL 15–18
Updated: October 2026 · Production-grade
RPO
How much data? · acceptable loss
RTO
How fast? · time to restore
Rule
3-2-1-1-0 · 3 copies, 2 media, 1 off-site
+1 / +0
immutable · 0 restore-test errors
Golden Rule
An untested backup = no backup
01

Fundamentals & Strategy

ConceptDescription
LogicalSQL / object-level dump. Version & platform independent, easy selective restore; slow restore on large data (indexes are rebuilt).
PhysicalBlock copy of the data files. Fast restore, fits TB scale; requires same major version + platform.
SnapshotLVM / ZFS / EBS / SAN snapshot. Crash-consistent; safe if WAL/redo is on the same volume or backup mode is used.
Full / Incr / DiffEverything / changes since last backup / since last full. The longer the incremental chain, the higher restore time and risk.
Hot / Warm / ColdReads+writes allowed / reads only / server stopped. Production target: hot.
Continuous ArchiveBase backup + continuous WAL stream → PITR: return to any moment. Brings RPO down to seconds.
Choosing a Method
ScenarioRecommendation
< 100 GB, version migration, single schemapg_dump -Fd -j N
TB scale, short RTOpgBackRest / pg_basebackup + WAL
RPO in secondsStreaming replica + pg_receivewal
"I deleted it by mistake" protectionPITR + recovery_min_apply_delay
Tool Map
Logicalpg_dump · pg_dumpall · pg_restore
Physicalpg_basebackup · pg_combinebackup (17+)
WALarchive_command · pg_receivewal · pg_waldump
EnterprisepgBackRest · Barman · WAL-G
Replication is not a backup: DROP TABLE or a bad UPDATE reaches every standby within milliseconds. HA ≠ DR.
02

Logical Backup

pg_dump FormatRestore ToolParallelSelective
-Fp plainpsql✗✗
-Fc custompg_restorerestore✓
-Fd directorypg_restoredump + restore✓
-Ft tarpg_restore✗✓
Parallel Dump & Global Objects
# 8 parallel jobs, zstd compression (PG16+) $ pg_dump -h db1 -U backup -Fd -j 8 -Z zstd:3 \ -f /bk/shop_$(date +%F) shop # Roles, tablespaces, settings $ pg_dumpall -h db1 -U postgres --globals-only \ > /bk/globals_$(date +%F).sql
Parallel & Selective Restore
$ psql -f globals.sql postgres $ createdb -O app shop $ pg_restore -j 8 -d shop --no-owner /bk/shop_dir # Single table only: $ pg_restore -l /bk/shop_dir > list.txt # edit it $ pg_restore -L list.txt -d shop /bk/shop_dir $ pg_restore -t orders -d shop /bk/shop_dir
ConsistencySingle snapshot (REPEATABLE READ) — does not block writes; a long dump holds back vacuum.
Version UpgradeAlways use the target version's pg_dump binary.
Caution: pg_dump does not include roles or tablespaces. Always pair it with pg_dumpall --globals-only — otherwise restore fails with "role does not exist".
03

Physical Backup & WAL Archiving

ParameterValue / Note
wal_levelreplica (default) — sufficient for PITR
archive_modeon — requires restart
archive_commandMust return 0 only on success; or archive_library (15+)
archive_timeout60s — RPO ceiling under low traffic
summarize_walon — for incremental backup (17+)
Base Backup + Verification
archive_command = 'test ! -f /arch/%f && cp %p /arch/%f' $ pg_basebackup -h db1 -U repl -D /bk/base_$(date +%F) \ -Ft -z -Xs -P -c fast $ pg_verifybackup /bk/base_2026-10-13 # manifest (13+)
Native Incremental (PG 17+)
$ pg_basebackup -D /bk/incr1 \ --incremental=/bk/full/backup_manifest # combine the chain before restore: $ pg_combinebackup /bk/full /bk/incr1 /bk/incr2 -o /restore
ToolHighlights
pgBackRestFull/diff/incr, parallel, compression + encryption, S3/GCS/Azure, verify
BarmanCentral backup server, streaming WAL, retention policy
WAL-GCloud-focused, delta backup, lightweight setup
Disk-Full Risk: archive_command keeps failing → WAL piles up in pg_wal → disk fills → server stops. pg_stat_archiver.failed_count needs an alert.
PostgreSQL Backup & Recovery Reference · PITR, Verification & Disaster Scenarios
04

Point-in-Time Recovery

Critical
Step by Step PITR
1Stop the server: systemctl stop postgresql
2Do not delete, move the old data directory — unarchived WAL in pg_wal may be needed.
3Extract the base backup (for tar: base.tar.gz + pg_wal.tar.gz), owner: postgres.
4Add restore_command + target parameters to postgresql.conf.
5touch $PGDATA/recovery.signal → start, watch the log.
6Check the data at the target → SELECT pg_wal_replay_resume(); to promote.
postgresql.conf (PG 12+)
restore_command = 'cp /arch/%f %p' recovery_target_time = '2026-10-13 09:59:59+03' recovery_target_inclusive = off recovery_target_action = 'pause' # pause|promote|shutdown recovery_target_timeline = 'latest' # default
TargetWhen?
recovery_target_timeIncident time is known (include the time zone!)
recovery_target_xidThe bad transaction ID is known
recovery_target_lsnpg_waldump exact WAL position found with
recovery_target_namepg_create_restore_point() marked point
recovery_target'immediate' — stop as soon as consistent
Pre-Deploy Marker & pgBackRest PITR
SELECT pg_create_restore_point('before_deploy_v42'); $ pgbackrest --stanza=main --delta --type=time \ --target="2026-10-13 09:59:59+03" \ --target-action=promote restore
Caution: Promotion starts a new timeline. Take a new base backup from the recovered server right away; the old chain does not cover the new timeline.
05

Verification, Testing & Monitoring

CheckTool / Method
Backup Integritypg_verifybackup (manifest), pgbackrest verify
Data IntegrityData checksums, pg_amcheck (14+), pg_checksums --check
Archive Healthpg_stat_archiver, count of .ready files
Restore TestAutomated, at least weekly; restore on a separate server → validate with queries → measure RTO and record it.
WAL Archive Health Check
SELECT archived_count, last_archived_wal, now() - last_archived_time AS since_last, failed_count, last_failed_wal, last_failed_time FROM pg_stat_archiver; -- WAL files waiting to be archived: SELECT count(*) FROM pg_ls_archive_statusdir() WHERE name LIKE '%.ready';
pgBackRest & Integrity Checks
$ pgbackrest --stanza=main check # archive + config test $ pgbackrest --stanza=main info # backups & WAL range $ pgbackrest --stanza=main verify $ pg_amcheck -d shop --heapallindexed -j 4
Set Up Alerts
Age of last successful backup > 26 hoursCritical
failed_count increase · .ready pile-upCritical
now() - last_archived_time > RPOCritical
Sudden ±30% change in backup sizeImportant
pg_wal / backup disk usage > 80%Important
PG 18: initdb now enables data checksums by default. On older clusters: pg_checksums --enable (with the server stopped).
06

Disaster Scenarios & Checklist

ScenarioSolution
Accidental DROP / DELETEPITR (recovery_target_xid / _time) or from a delayed standby via pg_dump -t
Disk / server lossPromote standby (pg_promote()) → new standby: pgbackrest --type=standby restore
RansomwareImmutable repo (S3 object lock) + repo-cipher-type + separate credentials
Corrupt pageChecksum / pg_amcheck detects it → restore the affected object from backup
Region losspgBackRest multi-repo (repo2 in another region) + DR runbook
Delayed Replica (1 hour)
# standby: postgresql.conf recovery_min_apply_delay = '1h' -- during an incident: pause replay, recover the data SELECT pg_wal_replay_pause();
RPO / RTO documented and signed off by the businessSLA → method choiceCritical
WAL archiving enabled and monitoredarchive_mode · pg_stat_archiverCritical
Automated restore testweekly · RTO measurementCritical
Off-site + encrypted copyrepo-cipher-type · S3 object lockCritical
Retention policy enforcedrepo1-retention-full · repo1-retention-diffImportant
Global objects backed uppg_dumpall --globals-onlyImportant
Config files version-controlledpostgresql.conf · pg_hba.conf · pgbackrest.confRecommended
DR runbook current and rehearsed2 drills per yearRecommended
Critical now Important this quarter Recommended plan it