Illustrative engineer at a laptop beside an encryption and isolated-restore teaching setup.

WIUNIX ACADEMY

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

Use protected credentials, a verified SFTP repository, explicit snapshot identity and an isolated restore with byte checks; separate repository checks from application recovery.

Advanced50 min readUpdated
Make this guide yours

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

THE OUTCOME

What will you have at the end?

Create a controlled encrypted offsite backup of a consistent export and verify restoration of the exact snapshot without overwriting the source.

Supported operating systems and versions

Documented restic0.19.1 stable workflow on Linux; no credentials, repository, backup or production dataset was accessed during authoring.

Before you begin

  • Existing supported restic, OpenSSH, OpenSSL, Python3 and GNU find/coreutils on an authorized Linux operator host
  • Dedicated SFTP backup account on an independent host, trusted host key and reviewed capacity
  • Existing stable application-consistent export, independently retained recovery key and a new isolated restore target
01

LET’S BUILD

Prepare the selected operator host

These are operator-host packages only. Review the transaction before approving; do not install tools inside application containers. The cluster, server and application binaries must already exist with owner-approved versions. Use Bash for the following panels. Package installation changes this selected host but starts no new application server.

This path targets an already verified upstream restic0.19.1 client (or a separately reviewed compatible build). Debian12 ships0.14 and Debian13 ships0.18: do not assume their distro restic satisfies this path. The package panel intentionally does not install/upgrade restic or enable EPEL. Obtain approval and verify the upstream release/signature through the documented installation process before using another binary.

bash

Selected operator host; approved package transaction

sudo apt-get update
sudo apt-get install --no-install-recommends ca-certificates curl bash coreutils openssh-client openssl python3 findutils
cat /etc/os-release
command -v bash curl timeout
restic version
ssh -V
python3 --version
openssl version
command -v sha256sum find sort xargs
How do I know it worked?

The OS/version matches the selection, required tools resolve, and existing application/client versions satisfy the stated compatibility boundary. Stop if a binary is missing or incompatible.

Preserve host security and existing paths

Ubuntu/Debian may use AppArmor and host firewall rules. This workflow opens no inbound host port and changes no security profile. For denied file/network access, inspect the owning service logs and policy with its administrator. Keep existing executable/config paths; public path fields are reviewed example locations, not discovered installation paths.

Define recovery before the first backup

A completed upload is not an application recovery. Define the source, consistency point, maximum acceptable data age and restore-time target. This recipe backs up an existing quiesced file export; it does not make live database files transaction-consistent. Use the database’s native backup/export procedure first and include its recovery metadata.

Offsite means a separate failure domain with independent access, not another directory on the same disk. Encryption protects repository content at rest, but a writer with delete access can still destroy backups. Append-only/immutable retention and independent credentials require backend-specific design; SFTP alone does not provide them.

02

LET’S BUILD

Verify version, destination and host identity

Use an existing supported restic client and dedicated backup SSH identity. Verify the server host key through a trusted independent channel before this step; never disable host checking. The target repository must be a new approved path whose parent is writable by the backup account. Inspect existing SSH restrictions with the owner, not by broadening permissions.

bash

Operator terminal; authorized lab context

restic version
ssh -o BatchMode=yes -o StrictHostKeyChecking=yes 'backup@backup.example.com' 'pwd'
ssh -o BatchMode=yes -o StrictHostKeyChecking=yes 'backup@backup.example.com' 'test ! -e /srv/restic/academy-repo'
How do I know it worked?

The verified host/account works and the exact new repository path does not exist; any access/error result stops initialization.

03

LET’S BUILD

Create a protected repository password without argv exposure

Create a new private local directory with restrictive permissions and generate a password directly into a file. It is never a public form value or command argument. Arrange an independently protected recovery copy of this password and SSH access through the existing secret-management process before relying on the backup. Losing all repository passwords/keys makes recovery impossible.

bash

Operator terminal; authorized lab context

umask 077
mkdir '/home/operator/academy-restic-private' || exit 1
openssl rand -base64 48 > '/home/operator/academy-restic-private/repository-password' || exit 1
chmod 600 '/home/operator/academy-restic-private/repository-password'
stat -c '%a %n' '/home/operator/academy-restic-private' '/home/operator/academy-restic-private/repository-password'
How do I know it worked?

The new directory is700 and password file600; no password value is displayed.

04

LET’S BUILD

Guard source and restore paths and estimate capacity

The source must be a stable readable export, and restore_dir must not exist. The guard rejects overlapping paths in either direction, a symlinked existing restore path and a filesystem root source. Check local restore capacity and remote repository capacity with the storage owner; deduplication ratios cannot be assumed. Public path fields do not prove filesystem ownership.

bash

Operator terminal; authorized lab context

python3 - <<'PY'
from pathlib import Path
import os
s=Path('/srv/backup-export').resolve(strict=True)
p=Path('/home/operator/academy-restic-private').resolve(strict=True)
assert p.is_dir(), 'Invalid private directory'
r=Path('/srv/academy-restic-restore')
assert s.is_dir() and s != Path(s.anchor), 'Invalid source'
assert not r.exists() and not r.is_symlink(), 'Restore target already exists'
r=r.resolve()
assert s != r and s not in r.parents and r not in s.parents, 'Overlapping source/restore'
assert p != s and s not in p.parents and p not in s.parents, 'Private directory overlaps source'
assert p != r and p not in r.parents and r not in p.parents, 'Private directory overlaps restore'
parent=r.parent.resolve(strict=True)
assert parent.is_dir() and os.access(parent,os.W_OK|os.X_OK), 'Restore parent is not writable'
print('Source/private/restore paths are non-overlapping; restore parent exists and is writable')
PY
du -xsh -- '/srv/backup-export'
df -h -- '/srv/backup-export' '/home/operator/academy-restic-private' "$(dirname -- '/srv/academy-restic-restore')"
How do I know it worked?

The paths are distinct and owned, the source is stable and capacity has been reviewed before upload.

Explore all tutorials ↗