Tutorials 10 min read

3-2-1 Backup with Restic: NAS + Encrypted Cloud Storage (2026)

Set up a real 3-2-1 backup with restic: an encrypted repository on your NAS over SFTP, an off-site copy in an S3-compatible bucket with restic copy, cron scheduling, and restore tests.

See our picks
S By Selfhostr Team · no sponsored content
3-2-1 Backup with Restic: NAS + Encrypted Cloud Storage (2026)
ⓘ ⓘ This article may contain affiliate links (no extra cost to you, it funds the site). As an Amazon Associate, Selfhostr earns from qualifying purchases. See the disclosure.
⏱️
~45 min
Duration
🎚️
Intermediate
Difficulty
💶
0 € (free software)
Cost
📦
Configured NAS, active cloud account
Key prerequisites

👍 What we like

  • ✓Genuinely follows the 3-2-1 rule, with an off-site copy.
  • ✓Client-side encryption: neither the NAS nor the cloud provider can read your data.
  • ✓Deduplication: after the first backup, only changes are transferred.
  • ✓Fine-grained restore of a single file or folder with restore --include or dump.
  • ✓Free and open-source software, no mandatory proprietary service.

👎 What to watch

  • ✕Command-line setup: no official graphical interface.
  • ✕Losing a repository password makes that repository unrecoverable.
  • ✕The server holds the credentials for both repositories: plan versioning or Object Lock against ransomware.
  • ✕Cloud storage and data re-reads (check) may be billed by the provider.

🏆 Our picks

Affiliate links · same price for you
Recommended
QNAP TS-453D

QNAP TS-453D

Voir sur Amazon

Amazon · price and availability on the listing

Alternative
Synology DiskStation DS1821+

Synology DiskStation DS1821+

Voir sur Amazon

Amazon · price and availability on the listing

See all our recommended gear in the shop.

📑 Contents ▾

The 3-2-1 rule calls for three copies of your data, on two different media, with one off-site. This tutorial sets it up with restic, a free and open-source backup tool that encrypts everything client-side. The architecture looks like this:

  1. Copy 1: the original data on your Linux server (for example /etc, /home, /srv).
  2. Copy 2: a restic repository on your NAS, reached over SFTP on the local network.
  3. Copy 3: a second restic repository in an S3-compatible bucket (Amazon S3, Backblaze B2’s S3 API, Wasabi, an off-site MinIO…), filled by restic copy from the NAS repository.

restic encrypts both repositories before anything is sent, so neither the NAS nor the cloud provider stores anything readable without the password. restic reads no YAML configuration file. Everything goes through command-line options and environment variables (RESTIC_PASSWORD_FILE, AWS_ACCESS_KEY_ID…), which is why you’ll write a small shell script below.

Prerequisites

  • A recent Linux server with root or sudo access: Debian 12/13 or Ubuntu 22.04/24.04. Commands are run as root, because the scheduled job will run as root to read every file.
  • A NAS on the local network, with the SFTP service enabled and a dedicated shared folder (for example backup). A QNAP TS-453D or a Synology DiskStation DS1821+ works as the SFTP target for copy 2. But the NAS alone does not satisfy the 3-2-1 rule: it stays in your home and is not the off-site copy.
  • An account with an S3-compatible storage provider, with an empty bucket and an access key restricted to that bucket. For Backblaze B2, the restic documentation recommends the S3-compatible API rather than the native b2: backend.
  • No inbound ports need to be opened: the server connects to the NAS over the LAN and reaches the cloud outbound over HTTPS.

Step-by-step installation

Step 1: Install restic

The distribution package is the easiest to install, but it often lags behind the current version (0.19.1 at the time of writing). Install it, then fetch the current official binary into /usr/local/bin, without overwriting the file managed by dpkg:

sudo apt update
sudo apt install -y restic
sudo restic self-update --output /usr/local/bin/restic
hash -r
restic version

If self-update isn’t available in your distribution’s package, install the official binary by hand (set VER to the latest release on GitHub, and change amd64 to arm64 if needed):

mkdir -p /tmp/restic-dl && cd /tmp/restic-dl
VER=0.19.1
curl -LO "https://github.com/restic/restic/releases/download/v${VER}/restic_${VER}_linux_amd64.bz2"
curl -LO "https://github.com/restic/restic/releases/download/v${VER}/SHA256SUMS"
sha256sum --ignore-missing -c SHA256SUMS
bunzip2 "restic_${VER}_linux_amd64.bz2"
sudo install -m 755 "restic_${VER}_linux_amd64" /usr/local/bin/restic

Check: command -v restic must return /usr/local/bin/restic, and restic version the expected version. sha256sum must print OK for the downloaded file.

Step 2: Prepare SFTP access to the NAS

Enable SFTP on the NAS:

  • Synology DSM 7: Control Panel > File Services > FTP, tick Enable SFTP service, then Apply.
  • QNAP QTS 5.1.8+ / QuTS hero: Control Panel > Network & File Services > Telnet / SSH, tick Enable SFTP, then Apply.

Create a dedicated user on the NAS (for example backup) with write access to the backup shared folder only. Then, on the server, generate an SSH key without a passphrase (cron must be able to use it) and declare an alias:

sudo ssh-keygen -t ed25519 -N "" -f /root/.ssh/id_ed25519_restic
sudo tee -a /root/.ssh/config >/dev/null <<'EOF'
Host nas
    HostName 192.168.1.20
    User backup
    Port 22
    IdentityFile /root/.ssh/id_ed25519_restic
    IdentitiesOnly yes
EOF
sudo chmod 600 /root/.ssh/config
sudo ssh-copy-id -i /root/.ssh/id_ed25519_restic.pub nas

Replace 192.168.1.20 with your NAS’s address. If ssh-copy-id fails, see your NAS documentation for how to enable key-based authentication. On DSM, you usually need to enable the user home service.

Check: sudo sftp nas must open a session without asking for a password. Type ls: the shared folder should appear (on DSM, shares show up as /backup). Note the exact path, then exit with bye.

Step 3: Create the secret files

Each repository gets its own password, stored in a file only root can read. The S3 credentials go into an environment file:

sudo install -d -m 700 /etc/restic
sudo sh -c 'openssl rand -base64 32 > /etc/restic/nas.pass'
sudo sh -c 'openssl rand -base64 32 > /etc/restic/cloud.pass'
sudo tee /etc/restic/cloud.env >/dev/null <<'EOF'
AWS_ACCESS_KEY_ID=YOUR_KEY_ID
AWS_SECRET_ACCESS_KEY=YOUR_SECRET
AWS_DEFAULT_REGION=eu-central-1
CLOUD_REPO=s3:https://YOUR_PROVIDER_S3_ENDPOINT/BUCKET_NAME/restic
EOF
sudo sh -c 'chmod 600 /etc/restic/*'

Take the endpoint and region from your provider’s console. On Backblaze B2, the S3 endpoint is listed in the bucket details. Copy both passwords into a password manager right away, off the server: without them, the repositories cannot be recovered.

Check: sudo ls -l /etc/restic must show three files (cloud.env, cloud.pass, nas.pass) as -rw------- root root.

Step 4: Initialize the NAS repository (copy 2)

sudo restic -r sftp:nas:/backup/restic --password-file /etc/restic/nas.pass init

Adjust /backup/restic to the path you noted in step 2. restic creates the restic folder itself.

Check:

sudo restic -r sftp:nas:/backup/restic --password-file /etc/restic/nas.pass cat config

The command should print JSON containing "version" and "chunker_polynomial".

Step 5: Initialize the cloud repository (copy 3)

The cloud repository is created with the same chunker parameters as the NAS repository, so deduplication works during restic copy. These parameters can’t be changed after creation.

sudo bash -c 'set -a; . /etc/restic/cloud.env; set +a
restic -r "$CLOUD_REPO" --password-file /etc/restic/cloud.pass init \
  --from-repo sftp:nas:/backup/restic --from-password-file /etc/restic/nas.pass \
  --copy-chunker-params'

Check: your provider’s console should show a restic/ prefix in the bucket containing config, keys/, and so on. The same command with snapshots in place of init ... should run without errors (no snapshots yet).

Step 6: Write the backup script

First, create the exclusion list:

sudo tee /etc/restic/excludes.txt >/dev/null <<'EOF'
/home/*/.cache
/var/tmp
*.tmp
EOF

Then create /usr/local/sbin/restic-321.sh. It backs up to the NAS, applies retention, copies the snapshots to the cloud, then applies the same retention on the cloud side:

sudo tee /usr/local/sbin/restic-321.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
exec >>/var/log/restic-321.log 2>&1
trap 'echo "$(date -Is) FAILED (line $LINENO)"' ERR
echo "=== $(date -Is) start ==="

NAS_REPO="sftp:nas:/backup/restic"
NAS_PASS=/etc/restic/nas.pass
set -a; . /etc/restic/cloud.env; set +a
KEEP=(--keep-daily 7 --keep-weekly 4 --keep-monthly 12)

restic -r "$NAS_REPO" --password-file "$NAS_PASS" backup \
  /etc /home /srv --exclude-file /etc/restic/excludes.txt
restic -r "$NAS_REPO" --password-file "$NAS_PASS" forget "${KEEP[@]}" --prune

restic -r "$CLOUD_REPO" --password-file /etc/restic/cloud.pass copy \
  --from-repo "$NAS_REPO" --from-password-file "$NAS_PASS"
restic -r "$CLOUD_REPO" --password-file /etc/restic/cloud.pass forget "${KEEP[@]}" --prune

echo "=== $(date -Is) end OK ==="
EOF
sudo chmod 700 /usr/local/sbin/restic-321.sh
sudo /usr/local/sbin/restic-321.sh

Change /etc /home /srv to your own data. A path that doesn’t exist makes backup exit with an error code, and the script then stops. The first run can take a while: everything is uploaded once, and after that only changes are sent.

Check: sudo tail -n 20 /var/log/restic-321.log must end with end OK. Then:

sudo restic -r sftp:nas:/backup/restic --password-file /etc/restic/nas.pass snapshots
sudo bash -c 'set -a; . /etc/restic/cloud.env; set +a; restic -r "$CLOUD_REPO" --password-file /etc/restic/cloud.pass snapshots'

Both lists must contain the same snapshot: same date, same paths.

Step 7: Schedule the daily backup

sudo tee /etc/cron.d/restic-321 >/dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 2 * * * root flock -n /run/restic-321.lock /usr/local/sbin/restic-321.sh
EOF
sudo chmod 644 /etc/cron.d/restic-321

flock -n prevents two runs at the same time, for example if a prune takes longer than expected. Output is not sent to /dev/null: the script writes to /var/log/restic-321.log itself.

Check: the next day, sudo journalctl -u cron --since yesterday | grep restic-321 should show the run, and the log should contain a new end OK line. To test without waiting, temporarily set the time a few minutes ahead.

Step 8: Test a restore

A backup that has never been restored is not a backup. Restore a folder from the cloud (the worst case) into a temporary directory, then compare:

sudo bash -c 'set -a; . /etc/restic/cloud.env; set +a
restic -r "$CLOUD_REPO" --password-file /etc/restic/cloud.pass \
  restore latest --target /tmp/restore-test --include /etc/ssh'
sudo diff -r /etc/ssh /tmp/restore-test/etc/ssh && echo "Restore identical"
sudo rm -rf /tmp/restore-test

Check: Restore identical should be printed. A difference can be normal if the file changed since the backup.

Verification checks

Every so often, for example once a month, run an integrity check on both repositories. --read-data-subset actually re-reads part of the data, not just the metadata:

sudo restic -r sftp:nas:/backup/restic --password-file /etc/restic/nas.pass check --read-data-subset=10%
sudo bash -c 'set -a; . /etc/restic/cloud.env; set +a; restic -r "$CLOUD_REPO" --password-file /etc/restic/cloud.pass check --read-data-subset=5%'

Expected result: no errors were found. On the cloud side, re-reading data generates egress traffic, which some providers charge for, so keep the percentage modest.

Security and best practices

  • Never expose to the Internet the NAS admin interface (DSM, QTS), its SSH or its SFTP, and set up no port forwarding to them. For remote administration, go through a mesh VPN: see our Tailscale tutorial.
  • Lost password = lost data: restic offers no way to recover it. Keep both passwords off the server.
  • Least privilege: the NAS backup user can only write to its share, and the S3 key only has access to its bucket.
  • Ransomware: the server holds the credentials for both repositories, so an attacker who compromises it could delete them. To protect yourself, enable bucket versioning or Object Lock if your provider offers it, as well as NAS snapshots. Check how they work with prune in your provider’s documentation and in the restic documentation (“append-only” section).
  • Updates: sudo apt update && sudo apt upgrade for the system, sudo restic self-update --output /usr/local/bin/restic for restic.

Troubleshooting common errors

  • Symptom: Fatal: unable to open config file / Is there a repository at the following location? Cause: wrong SFTP path or S3 URL, or the repository was never initialized. Fix: check the path with sudo sftp nas then ls, and check CLOUD_REPO (format s3:https://endpoint/bucket/prefix, path-style).
  • Symptom: subsystem request failed on channel 0 or connection refused. Cause: SFTP is disabled on the NAS, or the port is wrong. Fix: re-enable SFTP (menu paths in step 2) and check Port in /root/.ssh/config.
  • Symptom: wrong password or no key found. Cause: you are using one repository’s password file on the other. Fix: nas.pass goes with the NAS repository, cloud.pass with the cloud repository (and --from-password-file with the source of copy).
  • Symptom: Access Denied or a region error on the S3 side. Cause: the key has no rights on the bucket, or the region is wrong. Fix: generate a new key restricted to the bucket and set AWS_DEFAULT_REGION to the value your provider gives.
  • Symptom: repository is already locked. Cause: another operation is running, or a run was interrupted. Fix: check with pgrep -a restic that no restic process is running, then run restic -r ... unlock on the repository concerned.

FAQ

Are the backups encrypted?

Yes. restic encrypts and authenticates all data on the server before sending it (AES-256 in counter mode, authenticated with Poly1305-AES, according to its design documentation). The NAS and the cloud provider only see encrypted blobs.

Why restic copy instead of two independent backups?

Files are read only once, and both repositories hold exactly the same snapshots. Thanks to --copy-chunker-params, only data missing from the cloud is transferred.

How do I restore a single file?

Find its path with restic -r ... ls latest, then run restic -r ... restore latest --target /tmp/r --include /path/to/file. To print the file to standard output, use restic -r ... dump latest /path/to/file.

How do I check that the cloud backups are intact?

restic check verifies the repository structure. Add --read-data-subset to re-read and verify part of the data actually stored.

Read also

Tags: backupresticnascloud

Related

Tutorials

Recycle an Old PC into a Home Server in 2026

Turn an old PC into a home server in 2026: Ubuntu Server LTS, official Docker, Nextcloud with MariaDB and Caddy for automatic HTTPS, plus a firewall and backups.

Read
Tutorials

Backup Your NAS to an External Drive in 2026: Rotation, Encryption and Control

Back up a Synology NAS to an external USB drive with Hyper Backup: AES-256 client-side encryption, Smart Recycle rotation, two-drive rotation, integrity checks and a restore test.

Read
Tutorials

Getting Started with Docker Compose in Your Home Lab for 2026

First Docker Compose stack for your homelab: Docker from the official repository, the Compose v2 plugin, Nginx, MariaDB and LAN-only phpMyAdmin, with valid files and a check at every step.

Read