Restore drill: prove your backup works in 15 minutes
A repeatable procedure to restore one file and one database from your newest backup into a scratch location, compare the result with the original and record how long it took.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
A restore drill answers one question: if the original disappeared right now, could I get it back, and how long would it take? You do not restore everything. You restore a small, random sample into a place that cannot hurt production, verify it byte for byte, and write the result down. Fifteen minutes once a month catches almost every silent backup failure.
Before you start
- Know where the newest backup is and how to authenticate to it (repository password, S3 keys, PBS fingerprint). If finding this takes more than two minutes, that is already a finding.
- Have a scratch directory with enough free space, for example
/tmp/restore-drill, and a scratch database you can drop afterwards. - Start a timer. The drill measures reality, including the time you spend looking for passwords.
Step 1: restore a single file from a file-level backup
Pick a file that changes often, such as a config file edited last week. With restic:
export RESTIC_REPOSITORY=/mnt/nas/restic
export RESTIC_PASSWORD_FILE=/etc/restic/password
restic snapshots --latest 1
restic restore latest --target /tmp/restore-drill --include /etc/nginx/nginx.conf
diff /etc/nginx/nginx.conf /tmp/restore-drill/etc/nginx/nginx.conf && echo IDENTICAL
--target keeps the restore away from the live file, and --include restores only that path, so the whole thing takes seconds. With Borg the equivalent is borg extract, run from inside the scratch directory because it extracts relative to the current directory:
cd /tmp/restore-drill
borg list /mnt/nas/borg --last 1
borg extract /mnt/nas/borg::$(borg list /mnt/nas/borg --last 1 --format '{archive}') etc/nginx/nginx.conf
diff /etc/nginx/nginx.conf etc/nginx/nginx.conf && echo IDENTICAL
If the file differs, check whether it was edited after the snapshot before you call it a failure. If the file is missing from the backup, the include or exclude list is wrong, and that is the most valuable thing a drill can find.
Step 2: restore a database dump into a scratch database
A dump that exists is not a dump that imports. For PostgreSQL, create an empty database and restore the newest custom-format dump into it:
Check before you run this
- Runs with administrator rights. Make sure you understand every line.
latest=$(ls -t /var/backups/postgresql/*.dump | head -n 1)
echo "$latest"
sudo -u postgres createdb restore_drill
sudo -u postgres pg_restore --dbname restore_drill --no-owner --exit-on-error "$latest"
sudo -u postgres psql -d restore_drill -c "SELECT count(*) FROM orders;"
sudo -u postgres psql -d app -c "SELECT count(*) FROM orders;"
--exit-on-error makes pg_restore stop at the first problem instead of printing warnings you might miss. Compare a row count or a max(updated_at) between the scratch database and production; the difference should match the age of the dump. For MySQL or MariaDB:
latest=$(ls -t /var/backups/mysql/*.sql.gz | head -n 1)
mysql -e "CREATE DATABASE restore_drill;"
gunzip -c "$latest" | mysql restore_drill
mysql -e "SELECT count(*) FROM restore_drill.orders; SELECT count(*) FROM app.orders;"
If the dump was created with --all-databases or contains CREATE DATABASE and USE statements, it will not land in restore_drill; restore it on a throwaway VM or container instead, never on the production server.
Step 3: verify from a different machine once a quarter
The monthly drill proves the data is intact. The quarterly drill proves you can reach it without the original server: on a laptop or a fresh VM, install the backup tool, fetch the credentials from wherever you keep them (password manager, sealed envelope, vault), and repeat Step 1. This catches the most common total failure: the only copy of the repository password lived on the server that died.
Step 4: record the result and clean up
Stop the timer and write one line into a log or wiki page: date, which backup, what was restored, identical or not, minutes taken, and anything that was harder than it should have been. Then remove the scratch copies.
Check before you run this
- Deletes files and folders without asking. Check the path before running.
- Runs with administrator rights. Make sure you understand every line.
- Deletes database data permanently.
rm -rf /tmp/restore-drill
sudo -u postgres dropdb restore_drill
mysql -e "DROP DATABASE restore_drill;"
Careful: dropdb and DROP DATABASE are irreversible; double-check that the name is the scratch database and not production before pressing Enter.
What a good drill log looks like
2026-10-14 restic nas /etc/nginx/nginx.conf identical 4 min ok
2026-10-14 pg_dump app orders=184233 vs 184250 ok 6 min pg_restore needed --no-owner
2026-09-16 restic nas /etc/ssh/sshd_config MISSING 12 min /etc excluded by typo in excludes.txt, fixed
Over time this log is also your evidence for audits and the honest basis for the recovery time you promise others.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0308 What is a backup snapshot? A snapshot is a frozen picture of a disk or system at one moment that you can roll back to if something… Beginner · Explains an idea · Any system Article Strategy & restore drills 15 Oct 2026
- 0336 Back up and restore a Docker named volume to a tar archive A bash script that copies the contents of a named Docker volume into a .tar.gz on the host, and restores an archive… Intermediate · Step by step · Linux, macOS Script Backup tools · Docker basics 15 Oct 2026
- 0362 The 3-2-1 backup rule, and why a backup you never restored is not a backup Three copies, two kinds of media, one off-site: what the rule really protects against, how it maps to real tools, and why… Beginner · Explains an idea · Any system Article Strategy & restore drills 15 Oct 2026
- 0364 Schedule PostgreSQL and MySQL dumps with systemd timers Write one dump script for PostgreSQL and MySQL, run it from a systemd service and timer instead of cron, keep two weeks… Intermediate · Step by step · Linux Article Backup tools · PostgreSQL 15 Oct 2026
- 0365 Proxmox Backup Server: first setup and first restore Install Proxmox Backup Server, create a datastore and a backup user, connect it to Proxmox VE as storage, run the first VM… Intermediate · Step by step · Linux Article Backup & restore · Backup tools 15 Oct 2026
- 0366 restic backup wrapper with tagging, retention and a weekly check A bash wrapper that runs restic backup on a fixed list of paths, applies a keep-daily/weekly/monthly policy with forget and prune, runs… Intermediate · Step by step · Linux, macOS Script Backup tools · Backups & syncing 15 Oct 2026
Repos in the wild
- restic: fast, encrypted, deduplicated backups to local disk, SFTP or cloud storage restic/restic The restic repository: a single Go binary that backs up to local disks, SFTP, S3, Backblaze B2, Azure, Google Cloud and anything rclone can reach, with encryption and deduplication always on. GitHub
- BorgBackup: deduplicating archiver with compression and authenticated encryption borgbackup/borg The BorgBackup repository: a Python and C backup tool that stores deduplicated, compressed, encrypted archives on local disks or over SSH, with mountable archives and append-only repositories. GitHub
- rclone: rsync for cloud storage, with 70+ providers rclone/rclone The rclone repository: a Go command-line tool that copies, syncs, mounts and checks files across local disks and more than 70 cloud and object storage providers, with optional encryption. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +4,813 chars