This episode automates backups: scheduling weekly fulls and daily differentials with cron or a systemd timer, while WAL archiving runs real-time via archive_command. You'll also determine the retention policy based on business RPO/RTO and repo1-retention-archive for WAL management in the repository.

A backup that depends on human memory is a backup that fails on the most important day. In episode 9 we could do recovery manually — but no one should have to remember to run backup. In episode 10 we automate: backup scheduling via cron or a systemd timer, plus a retention policy that determines how long everything survives.
Two time layers work here: WAL archiving running real-time (every WAL segment, all the time) and snapshot backups running on a schedule (weekly full, daily differential). Both are governed by different schedules and shouldn't be treated as the same thing.
The classic pattern balancing space, time, and restore complexity:
archive_command — no scheduling needed.With this pattern, RPO is determined by WAL (almost real-time) and RTO is determined by the size of the last backup that must be restored.
Crontab for the postgres user — not root:
0 1 * * 0 /usr/bin/pgbackrest --stanza=main --type=full backup
0 1 * * 1-6 /usr/bin/pgbackrest --stanza=main --type=diff backup0 1 * * 0 — Sunday at 01:00: full.0 1 * * 1-6 — Monday through Saturday at 01:00: differential.Note
Use the absolute path of the binary (/usr/bin/pgbackrest) in cron because the postgres user's PATH is often incomplete. Failing with pgbackrest: command not found is the most common cron mistake — and the most confusing one, because the command works when tested manually.
A systemd timer is more modern: it has logging, dependencies, and systemctl list-timers for auditing. Create two units:
[Unit]
Description=Weekly full backup pgBackRest
[Timer]
OnCalendar=Sun 01:00:00
Persistent=true
[Install]
WantedBy=timers.target[Unit]
Description=Full backup of the main stanza
[Service]
Type=oneshot
User=postgres
ExecStart=/usr/bin/pgbackrest --stanza=main --type=full backupCreate a duplicate for differential (pgbackrest-diff.timer/service) with OnCalendar=Mon..Sat 01:00:00, then enable them:
sudo systemctl daemon-reload
sudo systemctl enable --now pgbackrest-full.timer pgbackrest-diff.timer
systemctl list-timers pgbackrest-*Persistent=true makes the timer run a missed backup if the machine was off at the scheduled time — an important difference from cron, which simply skips it.
pgBackRest locks the stanza during a backup — two backups on the same stanza can't run in parallel. Don't schedule full and diff at the same hour. This isn't a design flaw: overlapping backups would confuse the chain and retention.
Before choosing numbers, define two business metrics:
archive_timeout. An RPO of "1 minute" means WAL must reach the repository that quickly.Retention answers two derived questions: how far back you must be able to recover, and how long the archive must be kept (for example for auditing).
[global]
repo1-retention-full = 2
repo1-retention-diff = 7
repo1-retention-archive-type = full
repo1-retention-archive = 2repo1-retention-full = 2 — at least 2 full backups are kept (for example: you can always recover to the state of 1-2 weeks ago).repo1-retention-diff = 7 — differentials referencing the latest full are kept (allowing restore to any day in the current week).repo1-retention-archive-type = full + repo1-retention-archive = 2 — WAL for restoring up to the last 2 fulls is kept; older WAL is expired.This is the option that's often forgotten. WAL no longer needed to restore still-kept backups is just wasted space — but too little WAL makes restoring to an old backup impossible. repo1-retention-archive aligns the WAL lifetime with the backup lifetime, preventing the repository from filling up with useless WAL.
sudo -u postgres pgbackrest infoThe wal archive min/max line shows the range of stored WAL. If the range keeps stretching without bound, repo1-retention-archive hasn't been applied correctly.
Warning
Retention done wrong: too short means you can't recover far back; too long means the repository bloats. Start from business requirements (30-day audit? 7-day recovery?) then translate them into retention numbers. Don't copy someone else's numbers without understanding the context.
After a week of operation, verify the entire chain:
sudo -u postgres pgbackrest info
systemctl list-timers pgbackrest-* | grep -E "NEXT|pgbackrest"
sudo -u postgres pgbackrest --stanza=main checkNow we have a self-running system: real-time WAL, weekly full, daily diff, automatic retention. The next episode makes all of this secure — with encryption.
Key takeaways:
Persistent=true.repo1-retention-archive prevents the repository from being flooded with unused WAL.In the next episode we'll secure the backups: encryption & security — encrypting the entire repository contents with repo1-cipher-type=aes-256-cbc and repo1-cipher-pass, managing the passphrase in a secret manager, and protecting the config file with 600 permissions. An unencrypted backup is a liability waiting for an incident!