WIUNIX ACADEMY
PostgreSQL PITR with pgBackRest: prove a named recovery target on an isolated clone
Page 2 of 3. Review the earlier steps and prerequisites before continuing.
YOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment7 editable values
LET’S BUILD
Write a named restore point, then remove only the marker
Create the named restore point BEFORE the synthetic delete. Use this name only once in the rehearsal WAL history. After the delete, force a segment switch and run the repository check to confirm required archive delivery.
If you see duplicate target names in your rehearsal history, choose a new public target name before repeating the exercise. A timestamp without timezone is an avoidable ambiguity; this example deliberately uses a name.
Source PostgreSQL host
'/usr/pgsql-18/bin/psql' -X -v ON_ERROR_STOP=1 -d 'academy_lab' <<'SQL'
SELECT pg_create_restore_point('academy_before_change');
DELETE FROM academy_pitr_probe.marker WHERE id = 1;
SELECT count(*) AS source_rows_after_delete FROM academy_pitr_probe.marker;
SELECT pg_switch_wal();
SQL
pgbackrest --stanza='academy' checkThe source marker count is zero and the archive check succeeds.
LET’S BUILD
Prepare an empty isolated restore root
On the isolated restore host as its PostgreSQL OS user, stop before this step if the chosen root exists, is a symlink or is a production mount. The parent must already be a private directory owned by this account, with adequate capacity.
Do not run this on the source. Confirm no PostgreSQL service manager watches the new directory and no production data/tablespace directories are mounted here. This worked example excludes external tablespaces; inspect the backup manifest and reject one that uses them.
The default parent must be PostgreSQL-owned with no group/world write permission. If your host lacks a suitable private parent, an administrator should provision a new dedicated rehearsal parent; do not change permissions on existing directories to bypass this guard.
Isolated restore host
parent=$(dirname -- '/var/lib/pgsql/academy-pitr-20261028')
test -d "$parent" && test ! -L "$parent" && test -O "$parent" && test -w "$parent" || exit 1
parent_mode=$(stat -c '%a' "$parent")
test $((8#$parent_mode & 8#022)) -eq 0 || exit 1
test ! -e '/var/lib/pgsql/academy-pitr-20261028' && test ! -L '/var/lib/pgsql/academy-pitr-20261028' || exit 1
umask 077
mkdir -- '/var/lib/pgsql/academy-pitr-20261028' || exit 1
mkdir -- '/var/lib/pgsql/academy-pitr-20261028/socket'
df -h '/var/lib/pgsql/academy-pitr-20261028'
'/usr/pgsql-18/bin/postgres' --version
pgbackrest --stanza='academy' infoAn empty private root exists on the isolated host; major version and space are checked.
LET’S BUILD
Restore the chosen backup to the named target
Use the existing protected repository configuration on the isolated host. Enter the exact completed label in the terminal; it is not a password. An empty label aborts. The explicit pg1-path overrides the source path and reset-pg1-host keeps the restore local.
The restore command writes the chosen new data directory. It must not use delta, force or an existing directory. Keep target-action=pause: inspect recovery before choosing promotion.
Isolated restore host
printf 'Completed backup label: '
IFS= read -r backup_label
test -n "$backup_label" || exit 1
pgbackrest --stanza='academy' --reset-pg1-host --pg1-path='/var/lib/pgsql/academy-pitr-20261028/data' --set="$backup_label" --type=name --target='academy_before_change' --target-action=pause --archive-mode=off restoreRestore completes into the new data directory; recovery.signal and target settings are present.
LET’S BUILD
Inspect restored configuration before startup
Inspect the restored main configuration, included files, auto configuration, HBA and symlinks locally. Configuration can contain secrets: never paste full output into a ticket or public chat. Reject external data paths, source SSL private-key paths, preload libraries missing here and production connection hooks.
The commands show paths for manual inspection, not automatic rewriting. Use a private editor to remove source-specific hooks/includes safely. Preserve the generated restore_command and recovery target. This guide assumes a self-contained data-directory configuration; distribution-managed external configs require the owning explicit config_file path.
Isolated restore host
find '/var/lib/pgsql/academy-pitr-20261028/data' -maxdepth 2 -type l -ls
ls -l '/var/lib/pgsql/academy-pitr-20261028/data/recovery.signal' '/var/lib/pgsql/academy-pitr-20261028/data/postgresql.auto.conf'
nano '/var/lib/pgsql/academy-pitr-20261028/data/postgresql.conf'
nano '/var/lib/pgsql/academy-pitr-20261028/data/postgresql.auto.conf'
nano '/var/lib/pgsql/academy-pitr-20261028/data/pg_hba.conf'No unexpected external paths or production connection hooks remain; recovery settings are preserved.
LET’S BUILD
Start privately and inspect the paused target
Start only this standalone directory with private socket, loopback TCP, archiving off and no streaming connection. Normal startup completion may wait for WAL; inspect the private log if it fails. Do not turn a missing WAL error into promotion.
The HBA must permit the local PostgreSQL account to connect through the private socket. Change neither production authentication nor network rules.
Isolated restore host
'/usr/pgsql-18/bin/pg_ctl' -D '/var/lib/pgsql/academy-pitr-20261028/data' -l '/var/lib/pgsql/academy-pitr-20261028/recovery.log' -o "-c port=55432 -c listen_addresses=127.0.0.1 -c unix_socket_directories=/var/lib/pgsql/academy-pitr-20261028/socket -c archive_mode=off -c primary_conninfo=" start
'/usr/pgsql-18/bin/psql' -X -v ON_ERROR_STOP=1 -h '/var/lib/pgsql/academy-pitr-20261028/socket' -p '55432' -d 'academy_lab' -c "SELECT pg_is_in_recovery(), pg_get_wal_replay_pause_state(); SELECT * FROM academy_pitr_probe.marker;"Recovery is true, pause state is paused and the before-change row is present.
LET’S BUILD
Distinguish a missing target from a bad backup
If the marker is missing, compare the chosen backup completion with target creation, database identity and exact target name. If recovery is waiting, examine the missing WAL filename and whether retention removed the required archive. A repository info listing alone does not prove a complete WAL chain.
Read the log privately and record only redacted error codes, WAL segment identity and timing. Wrong-major startup errors require compatible binaries, not a forced upgrade. Do not resume replay before collecting the paused-state evidence.
Isolated restore host
tail -n 80 '/var/lib/pgsql/academy-pitr-20261028/recovery.log'
pgbackrest --stanza='academy' info
'/usr/pgsql-18/bin/psql' -X -h '/var/lib/pgsql/academy-pitr-20261028/socket' -p '55432' -d 'academy_lab' -c "SELECT pg_is_in_recovery(), pg_last_wal_replay_lsn(), pg_get_wal_replay_pause_state();"The failure is classified using source/backup/target/WAL evidence; no blind promotion occurs.