pgBackRest is the backup tool we install by default on PostgreSQL clusters that need real point-in-time recovery, parallel backups, compression, and a multi-repo retention model that scales beyond a single dump file. It replaces the pg_dump/pg_basebackup + custom-script pattern with a single binary that talks directly to the cluster’s data directory and WAL stream. This article covers a clean install on Ubuntu 24.04 against a self-hosted PostgreSQL 16, dual-repo (local + S3), and the PITR drill we run on every engagement.
How to verify
After install, confirm the binary, the stanza, and the WAL archive plumbing:
pgbackrest version
pgbackrest --stanza=prod check
sudo -u postgres pgbackrest info
sudo -u postgres psql -c "SHOW archive_command;"
sudo -u postgres psql -c "SELECT pg_walfile_name(pg_current_wal_lsn());"
check validates the stanza configuration end-to-end: it issues a manual pg_switch_wal(), ensures the archive command lands a WAL segment in the repo, and tests connectivity. If check passes, your archive chain is real.
What’s happening
pgBackRest stores three things per stanza (a stanza is the name for one PostgreSQL cluster being backed up): full backups, differential/incremental backups, and archived WAL segments. PostgreSQL is told to push every completed WAL segment to pgBackRest via its archive_command. Restores combine a base backup with the WAL stream replayed to a target timestamp, LSN, or named restore point.
The “multi-repo” model is the differentiator: pgBackRest can write to up to four repos in parallel — typically local disk + S3 + a third remote — so a single backup produces multiple immutable copies. Repos can be configured with different retention policies; local can keep 7 days, S3 can keep 90, all from the same backup run.
The on-disk layout under the repo root is archive/<stanza>/<pg-version>-<system-id>/ for WAL and backup/<stanza>/ for base backups. Each backup directory has a manifest listing every file with checksum, size, and reference to which backup actually stores it (full vs incremental).
The procedure
-
Install pgBackRest on the PostgreSQL host. The official PostgreSQL APT repo carries it.
apt install -y pgbackrest -
Create the repository directory on local disk and on the S3-backed mount or via S3 directly.
install -d -m 0750 -o postgres -g postgres /var/lib/pgbackrest -
Configure pgBackRest in
/etc/pgbackrest.conf. The example below uses a local repo and an S3 repo simultaneously.[global] repo1-path=/var/lib/pgbackrest repo1-retention-full=2 repo1-retention-diff=4 repo2-type=s3 repo2-path=/pgbackrest repo2-s3-bucket=acme-pg-backups repo2-s3-endpoint=s3.us-east-1.amazonaws.com repo2-s3-region=us-east-1 repo2-s3-key=<key> repo2-s3-key-secret=<secret> repo2-cipher-type=aes-256-cbc repo2-cipher-pass=<long-random-string> repo2-retention-full=12 process-max=4 log-level-console=info log-level-file=detail start-fast=y compress-type=zst [prod] pg1-path=/var/lib/postgresql/16/main pg1-port=5432 pg1-user=postgres -
Wire PostgreSQL into the chain. Edit
postgresql.conf:archive_mode = on archive_command = 'pgbackrest --stanza=prod archive-push %p' archive_timeout = 60 max_wal_senders = 10 wal_level = replicaThen restart PostgreSQL.
-
Initialize the stanza and take the first full backup.
sudo -u postgres pgbackrest --stanza=prod stanza-create sudo -u postgres pgbackrest --stanza=prod check sudo -u postgres pgbackrest --stanza=prod --type=full backup sudo -u postgres pgbackrest info -
Schedule the rotation. Typical pattern: weekly full, daily differential, retention managed by
repo-retention-*settings.# /etc/cron.d/pgbackrest 30 02 * * 0 postgres pgbackrest --stanza=prod --type=full backup 30 02 * * 1-6 postgres pgbackrest --stanza=prod --type=diff backup -
Drill the PITR. The drill is the only proof the chain works.
systemctl stop postgresql@16-main rm -rf /var/lib/postgresql/16/main/* sudo -u postgres pgbackrest --stanza=prod --delta \ --type=time --target="2026-06-01 14:30:00 UTC" restore systemctl start postgresql@16-main sudo -u postgres psql -c "SELECT now(), pg_is_in_recovery();"
Operational notes
archive_commandfailure must alert immediately. PostgreSQL keeps WAL on disk when archiving fails, which fillspg_wal/and eventually halts writes. We monitorpg_stat_archiver.failed_countin Prometheus.process-maxis the parallelism knob. Set it to the number of cores you can spare; pgBackRest scales nearly linearly on full backups but the network or the S3 endpoint becomes the limit before CPU does.compress-type=zstis the modern default;gzis the old one. Re-evaluate when migrating from older deployments — the ratio and CPU cost are both better with zstd.- Restore time on multi-TB clusters is dominated by WAL replay, not file copy. Set
recovery_target_action=promoteand monitorpg_stat_replicationduring the drill. - The S3 cipher (
repo-cipher-type=aes-256-cbc) is independent of bucket-side SSE. Use it — bucket encryption protects against AWS, not against credential leak. - pgBackRest can back up a standby instead of the primary by pointing the stanza at the replica with
pg1-hostsettings. We do this on engagements where the primary cannot tolerate backup I/O.
In the engagements we run, pgBackRest is the PostgreSQL backup default. Local + S3 dual-repo, weekly full + daily differential + continuous WAL, retention as code, and a quarterly PITR drill into a scratch host where the support desk can browse the restored cluster before sign-off. The full integration into a managed environment is in Managed Operations.