Introduction
Backups that are unencrypted, unverified, or writable by the machine they protect are the ones that fail during a ransomware event. Restic is a single-binary backup tool that encrypts everything client-side (AES-256 with Poly1305-AES authentication), deduplicates data into content-addressed chunks, and supports many backends including S3, Backblaze B2, SFTP, and local disks.
In this guide you will:
- Install restic and initialise an encrypted repository in an S3-compatible bucket
- Back up selected paths with sensible excludes
- Automate backups with a systemd service and timer
- Apply a retention policy and prune old data
- Verify integrity and perform a real test restore
This pairs well with a broader plan such as the low-bandwidth strategies covered in our rural backup guide; restic's deduplication makes incremental uploads small, which matters on slow links.
Prerequisites
- A Debian/Ubuntu or RHEL-family host (commands below use
apt; adapt as needed) - A bucket and an application key scoped to only that bucket. Do not use your account master key.
- Strongly recommended: enable bucket versioning or Object Lock on the bucket so a compromised host cannot silently destroy history
- A long random passphrase for the repository. If you lose it, the data is unrecoverable. Store it in your password manager and a sealed offline copy.
Step 1: Install Restic
Distribution packages can lag. Check the version, and if it is old, use the official release binary.
sudo apt update && sudo apt install -y restic
restic version
# Self-update to the latest release if the packaged one is old
sudo restic self-updateStep 2: Store Credentials Securely
Keep secrets in a root-only environment file rather than in scripts or shell history.
sudo install -d -m 0700 /etc/restic
sudo tee /etc/restic/env >/dev/null <<'EOF'
RESTIC_REPOSITORY=s3:https://s3.us-west-004.backblazeb2.com/my-backup-bucket/host01
RESTIC_PASSWORD_FILE=/etc/restic/password
AWS_ACCESS_KEY_ID=REPLACE_WITH_KEY_ID
AWS_SECRET_ACCESS_KEY=REPLACE_WITH_APP_KEY
EOF
sudo chmod 0600 /etc/restic/env
# Generate a strong passphrase, then SAVE IT in your password manager
openssl rand -base64 36 | sudo tee /etc/restic/password >/dev/null
sudo chmod 0600 /etc/restic/password
sudo cat /etc/restic/passwordAdjust the endpoint for your provider. For AWS S3 use s3:s3.amazonaws.com/bucket/path.
Step 3: Initialise the Repository
sudo bash -c 'set -a; . /etc/restic/env; set +a; restic init'Restic prints created restic repository ... at s3:.... Using one sub-path per host (/host01) keeps repositories separate and simplifies retention.
Step 4: Define What to Back Up
Create an include list and an exclude list so the job is explicit and reviewable.
sudo tee /etc/restic/includes >/dev/null <<'EOF'
/etc
/home
/var/lib/docker/volumes
/srv
EOF
sudo tee /etc/restic/excludes >/dev/null <<'EOF'
/home/*/.cache
/home/*/node_modules
/var/lib/docker/volumes/*/_data/tmp
*.tmp
*.log
EOFFor databases, dump them first (for example pg_dump or mysqldump) into a directory that is in the include list; copying live database files gives inconsistent backups.
Step 5: Run a First Backup
sudo bash -c 'set -a; . /etc/restic/env; set +a;
restic backup --files-from /etc/restic/includes \
--exclude-file /etc/restic/excludes \
--one-file-system --tag scheduled --verbose'The first run uploads everything; later runs only send changed chunks.
Step 6: Automate with systemd
Create a service that backs up, then applies retention.
sudo tee /etc/systemd/system/restic-backup.service >/dev/null <<'EOF'
[Unit]
Description=Restic encrypted backup
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
Nice=10
IOSchedulingClass=idle
ExecStart=/usr/bin/restic backup --files-from /etc/restic/includes --exclude-file /etc/restic/excludes --one-file-system --tag scheduled
ExecStart=/usr/bin/restic forget --tag scheduled --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --keep-yearly 2 --prune
ExecStart=/usr/bin/restic check --read-data-subset=5%
EOF
sudo tee /etc/systemd/system/restic-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Nightly restic backup
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timerPersistent=true runs a missed job after the machine boots, which helps laptops and VMs that are not always on. The check --read-data-subset=5% step downloads a random sample of data each night, so the whole repository is gradually validated over time.
Step 7: Alert on Failure
A silent backup failure is worse than none. Add an OnFailure= hook to notify you.
sudo mkdir -p /etc/systemd/system/restic-backup.service.d
sudo tee /etc/systemd/system/restic-backup.service.d/notify.conf >/dev/null <<'EOF'
[Unit]
OnFailure=restic-notify@%n.service
EOF
sudo tee /etc/systemd/system/restic-notify@.service >/dev/null <<'EOF'
[Unit]
Description=Notify on failure of %i
[Service]
Type=oneshot
ExecStart=/usr/bin/curl -fsS -m 10 -d "restic backup failed on %H (%i)" https://ntfy.example.com/backups
EOF
sudo systemctl daemon-reloadReplace the URL with your own ntfy, webhook, or healthcheck endpoint. A dead-man's-switch service (pinging on success) also catches the case where the timer never fires.
Verification and Testing
Confirm the timer and snapshots
systemctl list-timers restic-backup.timer
sudo systemctl start restic-backup.service
journalctl -u restic-backup.service -n 50 --no-pager
sudo bash -c 'set -a; . /etc/restic/env; set +a; restic snapshots'
sudo bash -c 'set -a; . /etc/restic/env; set +a; restic stats latest'Test a real restore
A backup is only proven when you restore it. Do this on a schedule, ideally onto a different machine using only the passphrase and bucket credentials.
sudo bash -c 'set -a; . /etc/restic/env; set +a;
restic restore latest --target /tmp/restore-test --include /etc/hostname'
cat /tmp/restore-test/etc/hostname
# Or browse the repository like a filesystem
sudo bash -c 'set -a; . /etc/restic/env; set +a; mkdir -p /mnt/restic && restic mount /mnt/restic'Full integrity check
sudo bash -c 'set -a; . /etc/restic/env; set +a; restic check --read-data'This downloads all data and can incur egress fees, so run it occasionally rather than nightly.
Hardening Notes
- Limit blast radius: a compromised host holds credentials that can delete the bucket contents. Use bucket Object Lock/versioning, or an application key without
deleteFiles, and runforget --prunefrom a separate trusted machine. - Separate credentials per host so one breach does not expose every repository.
- Keep the passphrase off the host being backed up where possible, or at minimum in a root-only file as shown.
- Follow the 3-2-1 rule: restic offsite is one copy; keep a local copy too (
restic copycan replicate between repositories).
Troubleshooting
Fatal: unable to open config file ... Is there a repository at the following location?
The repository path or endpoint is wrong, or the repo was never initialised. Check RESTIC_REPOSITORY and run restic init.
Fatal: wrong password or no key found
The passphrase file does not match the repository. Confirm /etc/restic/password has no trailing whitespace added by an editor, and that you are pointing at the right repo path.
unable to create lock in backend: repository is already locked
A previous run was interrupted. Confirm no restic process is running, then clear stale locks with restic unlock.
403 or Access Denied from the bucket
The application key is scoped to a different bucket, lacks list/write capabilities, or the endpoint region is wrong. Recreate the key and verify the URL.
Backups are slow or saturate the link
Limit bandwidth with --limit-upload 2000 (KiB/s) in the ExecStart line, and schedule the job off-hours.
Timer never fires
Run systemctl list-timers --all and systemctl status restic-backup.timer. Make sure it was enabled with enable --now and the unit files have no typos (systemd-analyze verify /etc/systemd/system/restic-backup.*).
forget --prune takes a long time
Pruning rewrites packs. Run it weekly instead of nightly by moving it into a separate service and timer.
Summary
You now have an encrypted, deduplicated, automated offsite backup pipeline: a restic repository in S3-compatible storage, credentials locked down under /etc/restic, a systemd timer with retention and rolling integrity checks, failure alerting, and a documented restore procedure. The most important habit is to schedule regular restore tests and protect the repository passphrase and bucket from deletion. A backup you have never restored is only a hope.