← Start of tutorial

WIUNIX ACADEMY

Restic offsite backup: prove a specific snapshot restores before trusting it

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.

Ubuntu 26.10Reviewed against official documentation ·

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
05

LET’S BUILD

Initialize the exact new repository once

Only after the destination absence and recovery-key custody checks pass, initialize the encrypted repository. If it already exists, stop and determine ownership; do not replace its password file or reinitialize elsewhere to bypass a connection error. Reuse these two environment lines in later panels; they contain a file reference, not a password value.

bash

Operator terminal; authorized lab context

unset RESTIC_PASSWORD RESTIC_PASSWORD_COMMAND
export RESTIC_REPOSITORY='sftp:backup@backup.example.com:/srv/restic/academy-repo'
export RESTIC_PASSWORD_FILE='/home/operator/academy-restic-private/repository-password'
restic init
restic cat config
restic snapshots
How do I know it worked?

A new repository ID/config is reported and there are no snapshots yet; retain its identity privately.

06

LET’S BUILD

Record file hashes while the export is stable

Keep writers quiesced or use an immutable native export throughout manifest creation and backup. Hashes validate file bytes only, not ownership, ACLs, extended attributes or application recovery. The manifest stays outside source so it is not accidentally included in the same snapshot. Its file names may be sensitive; keep it private.

bash

Operator terminal; authorized lab context

set -o pipefail
(set -C; : > '/home/operator/academy-restic-private/source-sha256.txt') || exit 1
(cd '/srv/backup-export' && find . -xdev -type f -print0 | sort -z | xargs -0 -r sha256sum --) > '/home/operator/academy-restic-private/source-sha256.txt' || exit 1
test -s '/home/operator/academy-restic-private/source-sha256.txt' || { printf '%s\n' 'No regular files: stop and review source'; exit 1; }
How do I know it worked?

A nonempty private file-hash manifest represents the exact stable export selected for backup.

07

LET’S BUILD

Back up once and retain the exact snapshot ID

This panel writes an encrypted snapshot to the approved remote repository. one-file-system excludes nested mounted filesystems; inventory them first and use separate explicit sources if they are required. Any nonzero exit, including partial-backup status, is a failed acceptance. The JSON summary is private operational output, not public authoring evidence.

bash

Operator terminal; authorized lab context

unset RESTIC_PASSWORD RESTIC_PASSWORD_COMMAND
export RESTIC_REPOSITORY='sftp:backup@backup.example.com:/srv/restic/academy-repo'
export RESTIC_PASSWORD_FILE='/home/operator/academy-restic-private/repository-password'
(set -C; : > '/home/operator/academy-restic-private/backup-result.json') || exit 1
restic backup '/srv/backup-export' --host 'app1' --tag academy-restore --one-file-system --json > '/home/operator/academy-restic-private/backup-result.json'
backup_status=$?
test "$backup_status" -eq 0 || { printf 'Backup failed: %s\n' "$backup_status"; exit 1; }
python3 - <<'PY'
import json
from pathlib import Path
p=Path('/home/operator/academy-restic-private')
rows=[json.loads(line) for line in (p/'backup-result.json').read_text().splitlines() if line.strip()]
summaries=[r for r in rows if r.get('message_type')=='summary']
assert len(summaries)==1 and summaries[0].get('snapshot_id'), 'No unique successful summary'
snapshot=summaries[0]['snapshot_id']
assert len(snapshot)==64 and all(c in '0123456789abcdef' for c in snapshot), 'Invalid snapshot ID'
with (p/'snapshot-id').open('x') as f:f.write(snapshot+'\n')
print('Exact snapshot ID retained in protected file')
PY
How do I know it worked?

Exit status is0 and one full64-character snapshot ID is retained; the source remained stable.

08

LET’S BUILD

Separate metadata checking from reading backup data

Read the exact snapshot and perform a metadata check. A normal check does not read/decrypt every data pack. A full read-data check can be expensive and incur remote transfer costs; review repository size/budget and schedule through the existing owner process before choosing it. A subset check covers only its documented subset, not all data.

bash

Operator terminal; authorized lab context

unset RESTIC_PASSWORD RESTIC_PASSWORD_COMMAND
export RESTIC_REPOSITORY='sftp:backup@backup.example.com:/srv/restic/academy-repo'
export RESTIC_PASSWORD_FILE='/home/operator/academy-restic-private/repository-password'
snapshot=$(cat '/home/operator/academy-restic-private/snapshot-id')
restic snapshots "$snapshot"
restic check
# Optional only after capacity/transfer-budget review:
restic check --read-data
How do I know it worked?

The exact snapshot exists and checks report no errors; record whether full data was actually read.

09

LET’S BUILD

Restore into a new isolated directory

This restores data locally and may consume substantial space. Re-run the non-overlap/capacity checks if anything changed, and use exclusive mkdir so an existing directory is never a restore target. Absolute snapshot source paths are reproduced below the target: the restored source is restore_dir followed by source_dir. Do not point restore at the live application path.

bash

Operator terminal; authorized lab context

unset RESTIC_PASSWORD RESTIC_PASSWORD_COMMAND
export RESTIC_REPOSITORY='sftp:backup@backup.example.com:/srv/restic/academy-repo'
export RESTIC_PASSWORD_FILE='/home/operator/academy-restic-private/repository-password'
mkdir -m 700 '/srv/academy-restic-restore' || exit 1
snapshot=$(cat '/home/operator/academy-restic-private/snapshot-id')
restic restore "$snapshot" --target '/srv/academy-restic-restore' --verify
How do I know it worked?

The selected snapshot restores successfully in the new separate target, with no source overwrite.

10

LET’S BUILD

Compare bytes and then exercise the application export

Verify all manifest-listed regular files under the restored source. This does not detect extra files or verify every metadata attribute; compare file counts and required permissions/ACLs separately. If the source export represents a database, restore it into an isolated database with the native recovery tooling and run application-level integrity checks before claiming recovery. Never attach the rehearsal instance to production queues or outbound delivery.

bash

Operator terminal; authorized lab context

cd '/srv/academy-restic-restore/srv/backup-export' || exit 1
sha256sum --check '/home/operator/academy-restic-private/source-sha256.txt' || exit 1
find . -xdev -type f -printf '.' | wc -c
find '/srv/backup-export' -xdev -type f -printf '.' | wc -c
How do I know it worked?

All selected hashes match and the reviewed file inventory/metadata and application restore checks meet the recovery contract.

Explore all tutorials ↗