Sign in
← Backups
15 min · medium · Lesson 8 of 9

restic in practice: repository, snapshots, restore

File backup with deduplication and encryption: init, backup, snapshots, restore, forget with prune, and check.

Why restic

Server files (user uploads, configs, database dumps) need to go somewhere, and plain rsync falls short here: it doesn't keep version history, doesn't deduplicate, and doesn't encrypt anything.

restic does all of this out of the box. It splits data into blocks and stores each unique block once. Everything is encrypted on the client side. It can write to a local folder, over SFTP, to S3, and compatible storage. borg has the same set of properties; choosing between them is a matter of taste.

You can install restic yourself following the official instructions, but you don't need to for this lesson's labs: restic is already in the container.

Repository

A repository is a folder or bucket that holds encrypted blocks and metadata. You create it once:

export RESTIC_PASSWORD='a-long-password'
restic -r /backup/repo init
created restic repository 823f8ee661 at /backup/repo

Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.

The warning is literal. The password is the encryption key, and support can't recover it for you. Keep the password in a secrets manager, and in scripts pass it through a file and the RESTIC_PASSWORD_FILE variable. The repository path can also be set with the RESTIC_REPOSITORY variable, so you don't need the -r flag.

First backup

restic -r /backup/repo backup /var/lib/app
no parent snapshot found, will read all files

Files:           3 new,     0 changed,     0 unmodified
Dirs:            3 new,     0 changed,     0 unmodified
Added to the repository: 192.082 KiB (191.578 KiB stored)

processed 3 files, 190.040 KiB in 0:00
snapshot a304a096 saved

190 KiB read, and about the same written: the repository is empty, there's nothing to save on yet. The result of a backup is called a snapshot, and each one has its own ID.

Second backup: deduplication shows up

Add one line to the small users.csv file and repeat the command:

Files:           3 new,     0 changed,     0 unmodified
Dirs:            3 new,     0 changed,     0 unmodified
Added to the repository: 2.067 KiB (1.397 KiB stored)

processed 3 files, 190.048 KiB in 0:01
snapshot 2f2cff34 saved

190 KiB processed again, but only about 2 KiB added to the repository. The identical blocks are already there, and nobody writes them a second time. That's why thirty daily snapshots take up only a bit more than one.

The blocks themselves are large: restic splits files into chunks from 512 KiB to 8 MiB. A file smaller than 512 KiB becomes a single block, so any edit to it writes the whole thing again. On small files the savings come from the files that didn't change.

A snapshot stays self-contained: you can restore any of them, no need to assemble a chain.

The parent snapshot gotcha

Look at the two lines above again: no parent snapshot found and 3 new instead of 1 changed. The files didn't change, but restic reread all of them.

The reason is that restic looks for the previous snapshot, the parent, by host and paths. And the run happened in containers, where the host name is new every time:

ID        Time                 Host          Tags        Paths         Size
----------------------------------------------------------------------------------
a304a096  2026-10-09 17:57:55  287cc06da6db              /var/lib/app  190.040 KiB
2f2cff34  2026-10-09 17:58:04  4b8743661a94              /var/lib/app  190.048 KiB

Fixed with the --host flag and a consistent name:

restic -r /backup/repo backup --host web-01 /var/lib/app

The first run with the new host name still won't find a parent: there are no snapshots with web-01 yet. From the second run on, things look normal:

using parent snapshot 3d090790

Files:           0 new,     1 changed,     2 unmodified
Dirs:            0 new,     3 changed,     0 unmodified
Added to the repository: 2.081 KiB (1.413 KiB stored)

processed 3 files, 190.062 KiB in 0:00
snapshot d1b9b6b2 saved

One file changed, two untouched. Deduplication worked even without this, but extra disk reads get expensive on large volumes.

Listing snapshots and restoring

restic -r /backup/repo snapshots
restic -r /backup/repo restore d1b9b6b2 --target /restore
restoring snapshot d1b9b6b2 of [/var/lib/app] at 2026-10-09 17:58:16.305728838 +0000 UTC by root@web-01 to /restore
Summary: Restored 6 files/dirs (190.062 KiB) in 0:00

Instead of the latest snapshot's ID, restic also accepts the word latest. Restoring puts data inside the given --target with the full original path underneath it (/restore/var/lib/app/...). The original is left untouched, which is the right call: it's easier to work things out on a copy.

Pulling out a single file without unpacking everything:

restic -r /backup/repo restore d1b9b6b2 --target /restore --include /var/lib/app/app.conf
Summary: Restored 4 / 1 files/dirs (26 B / 26 B) in 0:00

This kind of selective restore covers the most common request: "bring back the one config I broke yesterday." If you just need to look at a file's content from a snapshot, restic dump <ID> /var/lib/app/app.conf is enough: it prints the file to stdout. And restic diff <ID1> <ID2> shows which files changed between two snapshots.

Retention: forget and prune

Retention is the set of rules for how many snapshots to keep. forget removes records of snapshots by rule, prune clears out blocks nobody references anymore. They're usually run together:

restic -r /backup/repo forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune

There's a non-obvious behavior waiting here. The simple command

restic -r /backup/repo forget --keep-last 2 --prune

on the repository of four snapshots from above left four, not two. restic groups snapshots by host and paths (--group-by host,paths by default) and applies the rule inside each group. There are three groups here: two random container names with one snapshot each, and web-01 with two. Each group kept up to two snapshots.

In practice that's convenient: a "7 days" policy applies separately to each server. But if you expected exactly two snapshots across the whole repository, the result is surprising. Change this with the --group-by '' flag.

Before running this for real, --dry-run is worth using: it shows what would be deleted without touching anything.

Checking

A copy only counts as a copy after it's been checked, and restic has a command for that:

restic -r /backup/repo check --read-data
load indexes
check all packs
check snapshots, trees and blobs
[0:00] 100.00%  4 / 4 snapshots
read all data
[0:00] 100.00%  8 / 8 packs
no errors were found

Without --read-data, only the repository's structure gets checked: indexes, snapshots, and trees. That's fast. With the flag, restic reads and verifies every data block. On terabytes that's slow and expensive in bandwidth, so a full check usually runs on a monthly schedule, with a partial one (--read-data-subset=1/10) more often.

check verifies the repository's integrity, not whether the data is usable. A real test is restoring it and running whatever got restored.

What this looks like in production

#!/bin/sh
set -e
export RESTIC_REPOSITORY=s3:s3.example.com/backups
export RESTIC_PASSWORD_FILE=/etc/restic/pass

pg_dump -Fc mydb > /var/backups/mydb.dump
restic backup --host web-01 /var/backups /var/lib/app
restic forget --host web-01 --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check

The script runs from cron, its output goes to a log, and the log feeds monitoring. Separately watch for whether the backup ran at all: silence in the logs usually means not "everything's fine" but "the script didn't run." The --host flag on forget scopes retention to that server's snapshots, so a single host's script doesn't clean out other hosts' snapshots in a shared repository.

Common mistakes

Lost the password. The password is the encryption key. Lose it, and the data is gone for good. Keep a copy of the password separate from the server you're backing up.

No consistent --host. In containers and on machines that get recreated, restic doesn't find the parent snapshot and rereads everything. The sign in the output: no parent snapshot found on every run.

Restored the whole snapshot when you needed one file. A full restore over the working folder also rolls back anything that changed since the backup. For a single file, there's --include and restic dump.

forget left more snapshots than expected. The rule applies inside the "host plus paths" group. Check the Host column in restic snapshots and verify the policy with --dry-run.

check without restoring. check --read-data confirms the repository is intact, not that a working application will come up from it.

What you'll need for the tasks

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson backup-basics-08-restic