The full-system restore is the one that gets rehearsed. The
partial restore is the one that actually happens: somebody
deleted a config file, a deployment overwrote a template, a
table was truncated by a migration that was supposed to be
read-only.
It has a completely different risk profile. The host is up. The
service is running. Other people’s changes are live in the same
directory you are about to write into. The failure mode is not
“the restore did not work” - it is “the restore worked and took
three weeks of unrelated changes with it”.
Rule one: never restore onto the live path
Configuration changestage first— A staging directory named after the ticket, on the same filesystem as the target so the final move is atomic, mode 700 because the restored files may include secrets and the staging directory has no reason to be readable. Everything else in this lesson extracts here first. The only step that touches the live path is a reviewed move at the end.
Extracting into a staging path costs one directory and buys
three things: you can diff before committing, you can abandon
the restore with no trace if it turns out to be the wrong
version, and the live service keeps running on the files it
already has while you decide.
Read-only / Safethe review step that saves the restore— Two findings in one diff. The backup predates a tuning change somebody made in July, so restoring the whole file reverts it. And sites-enabled/reporting.conf exists only on the live host - a site added after the backup, which a directory-level restore with --delete would remove. Neither is visible without this comparison, and both are what turns a five-minute restore into an incident.
Finding the right version is most of the work, and every
deduplicating backup tool can list and read individual paths
without restoring anything.
Read-only / Safewhich archives exist— borg list against the repository enumerates archives; borg list against a single archive enumerates its contents. Neither extracts anything. The restic equivalents are 'restic -r /backup/repo snapshots' and 'restic -r /backup/repo ls <snapshot-id> /etc/nginx'.
Configuration changebrowse the archive as a filesystem— borg mount presents an archive read-only over FUSE, so ordinary tools work on it: ls, diff, grep, cp. Nothing is extracted until a file is read, which makes it far cheaper than a full restore for finding the version you want. Unmount with 'borg umount /mnt/borg' when you are done - an idle FUSE mount holds a lock on the repository and blocks the next backup. restic has the same facility as 'restic -r /backup/repo mount /mnt/restic'.
$ borg mount /backup/repo::backup-2026-07-21T02:00:11 /mnt/borg
Finding the last good version, not the last version
The newest backup contains the damage. That is normally the
whole reason there is an incident: the file was corrupted or
deleted at some point, and every backup since has faithfully
captured that state.
Establish when the damage happened, from the application log, the deployment record or the shell history - not from the backup timestamps.
List the archives that bracket that moment. The last backup before it is the candidate.
Verify the candidate before committing to it. Read the file out of the archive and check it is actually intact - a slow corruption may predate the incident you noticed.
If the boundary is unclear, walk backwards. Compare consecutive archives and find where the file changes shape rather than content.
Read-only / Safewhat changed between two snapshots— restic diff compares two snapshots and reports added (+), removed (-) and modified (M) paths. It is the fastest way to bracket when a file changed without extracting either version, and it turns 'which backup do I want' from a guess into a lookup. Borg has no equivalent single command; compare 'borg list' output for the two archives instead.
M /etc/nginx/nginx.conf
+ /etc/nginx/sites-enabled/reporting.conf
Illustrative output
Point-in-time recovery for databases
“Restore the file” has no meaning for a database. The unit is a
consistent state at a moment, and reaching an arbitrary moment
needs two ingredients: a base backup taken before it, and the
write-ahead log covering the interval from the base backup to
the target time.
PostgreSQL calls it PITR and drives it from configuration, not
from a command:
# postgresql.conf on the RECOVERY instance - not the live onerestore_command = 'cp /backup/wal/%f %p'recovery_target_time = '2026-08-11 09:12:00+00'recovery_target_action = 'pause'
# the recovery.signal file is what puts the instance into# targeted recovery; PostgreSQL 12 and later have no recovery.confsudo -u postgres touch /var/lib/postgresql/16/restore/recovery.signal
MySQL and MariaDB reach the same place from the other direction:
restore the full backup, then replay the binary log up to the
moment.
Writing the replay to a file first, rather than piping it
straight into mysql, is the same staging discipline as the
file case. It is reviewable, it is re-runnable, and it does not
apply anything until somebody has looked at it.
Knowledge check
Knowledge check · 4 questions
Q1. You run `borg extract /backup/repo::backup-2026-07-21T02:00:11 /etc/nginx/nginx.conf` from your home directory. What happens?
Q2. Which of these are correct handling for a single-file restore onto a live production host? Select all that apply.
Q3. A PostgreSQL point-in-time recovery should be run against a separate data directory on a separate port, with recovery_target_action set to pause.
Q4. A file was corrupted at some unknown point in the last month. Why is the newest backup usually the wrong one to restore from?
Passing score: 75%. Answers are checked in this browser.