How I Use Restic to Back up My Home Folders to Backblaze B2

My guide to using the open source backup program restic to back up your home folders to Backblaze B2 cloud storage.

Backing up a new machine for the first time

1. Create a B2 bucket

All files in B2 are contained in buckets, which are like the top-level folders in your B2 account. Before you can back anything up to B2 you need to create a bucket: Sign in to the Backblaze website, navigate to the B2 Cloud Storage Buckets page, and click the Create a Bucket button:

Creating a B2 bucket.

Creating a B2 bucket.

Bucket names must be unique across all of B2, including the names of other user’s buckets! Hence the random numbers at the end of the bucket name.

Make sure the bucket is set to Private.

You can leave B2’s default encryption disabled: restic will encrypt your files for you.

One-to-one mapping between buckets and repositories

You’ll see below that I create one restic repository in each B2 bucket, at the root of the bucket. That’s why my RESTIC_REPOSITORY environment variable contains just a bucket name with no path (b2:restic-seanh-laptop-79539).

Alternatively you could create the restic repository in a subdirectory within the bucket. Just add a path to the end of your RESTIC_REPOSITORY like this: b2:bucketname:path/to/repo. This would allow a single B2 bucket to contain multiple restic repositories at different paths, and I think B2 application keys can be given access restricted to subpaths of buckets so different hosts don’t have access to each other’s backups.

I find it simpler to create a separate bucket for each restic repo. A B2 account is limited to 100 buckets so if you have a lot of repos you might want to try sharing buckets.

Shared versus separate repos

When backing up multiple computers they can all back up to the same restic repo or each computer can back up to its own separate repo.

Most of the time I create a separate B2 bucket and restic repo for each machine because it’s more secure: when machines share a restic repo they have full read write access to each other’s backups. Using separate repos also avoids locking: machines can back up to the same repo simultaneously but if one machine is running certain maintenance commands like restic prune or restic check then other machines can’t back up to (or run maintenance commands on) the same repo at the same time.

My main desktop and laptop do share the same repo however. This is more storage efficient because restic will deduplicate files across the two different hosts: if the desktop and laptop both contain copies of the same file only one copy of that data will actually be stored in the restic repository. In this case I think the security risk is acceptable: these machines have a lot of files in common so the deduplication might be significant. There’s already all sorts of shared sensitive files that I have on both of these machines anyway (my GPG and SSH keys, log ins to my Bitwarden account, etc). Both machines use full disk encryption. To avoid locking I run all my maintenance commands from one host.

When sharing a single restic repo between multiple hosts I create a separate application key (in Backblaze) and a separate RESTIC_PASSWORD (using the restic key command, see restic help key) for each machine. This means each machine’s access can be revoked individually if that machine is compromised or is no longer using the repo.

2. Create an application key for the B2 bucket

Restic needs a B2 application key to read and write to the bucket. Navigate to the Application Keys page and click the Add a New Application Key button:

Creating an application key.

Creating an application key.

In the Allow access to Bucket(s) dropdown select only the bucket you just created.

Type of Access must be set to Read and Write.

Copy the applicationKey that’s shown after you click Create New Key, you’ll need it later:

Copying the application key.

Copy the application key for later.

3. Install pass

We’re going to use pass to save encrypted copies of the application key and other restic settings on the host machine that’s going to be backed up. On the host machine:

  1. Install pass.

    On Ubuntu:

    $ sudo apt install pass
    

    On macOS install Homebrew and then run:

    $ brew install pass
    
  2. Create a GPG keypair for pass to encrypt things with:

    $ gpg --full-generate-key
    ...
    public and secret key created and signed.
    
    pub   rsa3072 2022-04-03 [SC]
          6346C69761B1155E228D488F8C23BC2681C72FBA
    uid                      Sean Hammond (GPG key for `pass` on seanh-laptop)
    sub   rsa3072 2022-04-03 [E]
    

    Take note of the key’s UID (the part beginning 6346... in my example), you’ll need it for the next step. (You can always retrieve it with gpg --list-secret-keys).

  3. Initialize the password store with the GPG key:

    $ pass init <GPG_KEY_ID>
    

4. Insert restic settings into pass

Insert the B2 bucket name, application key ID, and application key value into pass:

$ pass insert "$(hostname)"/RESTIC_REPOSITORY
<When prompted enter "b2:" followed by the name of the B2 bucket you created.>
<Example: b2:restic-seanh-laptop-79539>

$ pass insert "$(hostname)"/B2_ACCOUNT_ID
<When prompted paste in the keyID of the application key that you created.>

$ pass insert "$(hostname)"/B2_ACCOUNT_KEY
<When prompted paste in the secret value of the application key you created.>

We’re inserting the settings into a password store subfolder named after your hostname so that the password store can potentially be extended to hold settings for multiple restic repos, which we’ll be doing later.

5. Generate a RESTIC_PASSWORD and save it in Bitwarden and pass

restic also needs a RESTIC_PASSWORD setting which is the password it’ll use to encrypt your backups. I use the cloud password manager Bitwarden to back up the RESTIC_PASSWORD:

  1. Use pass to generate a password and save it in the local password store:

    $ pass generate "$(hostname)"/RESTIC_PASSWORD
    
  2. Also copy-paste the password into Bitwarden as a backup. pass can copy the password to the clipboard for you:

    $ pass show --clip "$(hostname)"/RESTIC_PASSWORD
    

Don’t lose your RESTIC_PASSWORD!

RESTIC_PASSWORD is a restic thing, Backblaze doesn’t know anything about it. While RESTIC_REPOSITORY, B2_ACCOUNT_ID and B2_ACCOUNT_KEY can all be retrieved or regenerated from the Backblaze website, RESTIC_PASSWORD can’t be. And since RESTIC_PASSWORD is what’s used to encrypt your backups, if you lose your RESTIC_PASSWORD you’ve lost your backups. This is why I save a copy of each RESTIC_PASSWORD in Bitwarden as well as in pass on the host machine.

6. Create an env script

Create a ~/.restic folder (mkdir -p ~/.restic) and create a ~/.restic/env.bash script to load the restic settings from pass into environment variables:

#!/bin/bash
set -euo pipefail

RESTIC_HOST="$(hostname)"
RESTIC_REPOSITORY="$(pass "$RESTIC_HOST"/RESTIC_REPOSITORY)"
B2_ACCOUNT_ID="$(pass "$RESTIC_HOST"/B2_ACCOUNT_ID)"
B2_ACCOUNT_KEY="$(pass "$RESTIC_HOST"/B2_ACCOUNT_KEY)"
RESTIC_PASSWORD_COMMAND=pass\ "$RESTIC_HOST"/RESTIC_PASSWORD

export RESTIC_HOST RESTIC_REPOSITORY B2_ACCOUNT_ID B2_ACCOUNT_KEY RESTIC_PASSWORD_COMMAND
RESTIC_HOST

RESTIC_HOST isn’t an environment variable recognised by restic itself. We’re exporting it because we’ll use it in our backup and maintenance scripts later on.

fish-shell

If you use fish-shell you might also want to create a ~/.restic/env.fish script that you can source from fish:

#!/usr/bin/fish

set -x RESTIC_HOST (hostname)
set -x RESTIC_REPOSITORY (pass $RESTIC_HOST/RESTIC_REPOSITORY)
set -x B2_ACCOUNT_ID (pass $RESTIC_HOST/B2_ACCOUNT_ID)
set -x B2_ACCOUNT_KEY (pass $RESTIC_HOST/B2_ACCOUNT_KEY)
set -x RESTIC_PASSWORD_COMMAND pass $RESTIC_HOST/RESTIC_PASSWORD

7. Source the env script

Now you can source the env.bash script from a bash shell and any subsequent restic commands in that shell session will use your B2 bucket, application key, and restic password without having to pass them as options to each command.

The rest of the commands in this post will assume you’ve sourced the env.bash script, so do that now:

$ source ~/.restic/env.bash

The script can also be sourced from other bash scripts, which we’ll do below.

8. Install restic

To install restic on Ubuntu:

$ sudo apt install restic
$ sudo restic self-update

$ # Install bash autocompletions for restic subcommands and options.
$ mkdir -p ~/.local/share/bash-completion
$ restic generate --bash-completion ~/.local/share/bash-completion/restic

The autocompletions enable you to press Tab after typing restic to autocomplete restic subcommands, or press Tab after -- to autocomplete restic options.

On macOS install Homebrew and then run:

$ brew install restic
fish-shell

If you use fish-shell you might also want to install fish-shell autocompletions:

$ mkdir -p ~/.config/fish/completions
$ restic generate --fish-completion ~/.config/fish/completions/restic.fish

9. Initialize a restic repo in the B2 bucket

Initialize a restic repository (repo) in the B2 bucket:

$ restic init
created restic repository 191d720a00 at b2:restic-seanh-laptop-79539

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

If you browse your bucket in Backblaze you should see that restic has created some housekeeping files in it:

Files created by restic init.

10. Create a backup script

Create a ~/.restic/backup script that runs a restic backup command to back up your home directory.

You’ll want to pass some command line options to restic backup, consider:

--compression max
Increase compression to save storage space on your backup location, at the cost of using more CPU.
--cleanup-cache
Remove old cache directories after running.
--tag <TAG>
Add a given tag to the snapshot that the backup command creates. Restic lets you add one or more tags (just arbitrary strings of your choosing) to snapshots. These can be handy to filter by when listing or removing snapshots. The example backup script below adds the tag .restic/backup to all snapshots it creates, and the maintenance script further below uses this tag to identify the backup script’s snapshots.
--one-file-system
Excludes any other file systems that’re mounted within your home directory.
--exclude-caches
Excludes directories that are marked as caches by having a CACHEDIR.TAG file.
--exclude <FILE>
Excludes unwanted files from the backup. --exclude can be given multiple times, and <FILE> can be a pattern. There are other options for excluding files as well, see Excluding Files in the restic docs for details. You might want to use Disk Usage Analyzer (baobab) to scan your home directory for large files or folders that you don’t want to back up.

See restic help backup for the rest of the available options.

Here’s an example backup script. This one uses your entire home directory ($HOME) as the path to back up, and uses --excludes to opt-out certain paths from the backup. Alternatively you could take an opt-in approach: instead of your entire $HOME list multiple more specific paths to backup by giving multiple positional arguments to the restic backup command (there are other options for including files as well, see Including Files in the restic docs for details).

#!/bin/bash
set -euo pipefail

source ~/.restic/env.bash

restic backup \
    --compression max \
    --cleanup-cache \
    --tag '.restic/backup' \
    --group-by 'host,tags' \
    --one-file-system \
    --exclude-caches \
    --exclude "$HOME/Downloads" \
    --exclude "$HOME/Library" \
    --exclude "$HOME/snap" \
    --exclude "$HOME/.Trash" \
    --exclude "$HOME/.bundle" \
    --exclude "$HOME/.cache" \
    --exclude "$HOME/.dbus" \
    --exclude "$HOME/.dropbox" \
    --exclude "$HOME/.dropbox-dist" \
    --exclude "$HOME/.local/pipx" \
    --exclude "$HOME/.local/share/Trash" \
    --exclude "$HOME/.npm" \
    --exclude "$HOME/.pyenv" \
    --exclude "$HOME/.thumbnails" \
    --exclude "$HOME/.virtualenvs" \
    --exclude "node_modules" \
    --exclude ".tox" \
    "$HOME"
The --group-by 'host,tags' option

The --group-by 'host,tags' option in the restic backup command above tells restic how to find the “parent” snapshot.

If a file has already been backed up in a previous snapshot (created by a previous restic backup command) restic avoids unnecessarily backing up the same file again. To do this it has to scan the entire contents of every file to see if they’re the same, which can take a long time.

So to speed things up restic picks one previous snapshot to be the “parent” snapshot and if a file exists in the parent snapshot that had the same path, mtime, ctime, size and inode number as a file to be backed up then restic assumes that’s the same file and skips backing it up again without scanning the entire contents of the file to make sure it’s really the same.

The criteria for deciding whether two files are the same are configurable, and the entire optimisation can also be disabled (forcing a full scan of all files), see File change detection in the restic docs for details.

The --group-by option tells restic how to choose the parent snapshot to use for this optimisation. By default it’s set to host,paths which means it’ll pick the latest snapshot with the same host and paths as the new snapshot being created. This means that if you change the paths in your backup script from one snapshot to the next then previous snapshots with different paths may not be eligible to be parent snapshots and restic can end up having to rescan a lot of files.

--group-by 'host,tags' tells restic to pick the latest snapshot with the same host and tags, and since our backup script always uses --tag '.restic/backup' this’ll pick the latest snapshot created by the script. host is there in case you use the same script to back up multiple hosts to the same repo with the same tag: snapshots from one host will only choose previous snapshots from that same host as parents.

11. Make the backup script executable

$ chmod u+x ~/.restic/backup

12. Do the initial backup

Finally, to backup your home directory to B2 just run:

$ ~/.restic/backup

This will take a long time to run. Once it’s finished you’ll see more files in your B2 bucket:

Files created by restic backup.

Updating a backup

To incrementally update a machine’s backup, uploading only new or modified files, just re-run the same backup script:

$ ~/.restic/backup

Interrupting and resuming a backup

You can interrupt a backup with Ctrl + c and later resume it by re-running the same ~/.restic/backup command. See Will restic resume an interrupted backup? in restic’s FAQ.

Limiting bandwidth usage

You can limit restic’s speed so it doesn’t hog all your bandwidth when you’re updating a backup, just add the --limit-download and --limit-upload options. You might not want to do this for your initial backup, but once your first backup has completed consider adding these to your backup script:

$ restic backup --limit-download 5600 --limit-upload 1800 ...

The values are the maximum rates in KiB/s.

Other commands like restic check (for verifying a backup) and restic restore (for restoring a backup) also support the the --limit-download and --limit-upload options.

Backing up just one file to the same repo

You can run a one-off command to quickly back up one or more specific files or directories to the same repo, without doing an incremental backup of the whole machine. Just pass the file path(s) to restic backup. This while create a separate snapshot that contains only the given file(s):

$ restic backup --verbose=3 foo.txt
open repository
repository d59c3d15 opened successfully, password is correct
lock repository
load index files
no parent snapshot found, will read all files
start scan on [foo.txt]
start backup on [foo.txt]
scan finished in 28.574s: 1 files, 4 B
new       /foo.txt, saved in 0.004s (4 B added)

Files:           1 new,     0 changed,     0 unmodified
Dirs:            0 new,     0 changed,     0 unmodified
Data Blobs:      1 new
Tree Blobs:      1 new
Added to the repo: 384 B

processed 1 files, 4 B in 0:35
snapshot c6d56f86 saved

Doing a dry run with restic backup --dry-run

You can always test what a restic backup command would do by passing the --dry-run and --verbose arguments. This can be good for testing whether your exclude options will do what you expect, or checking how much data will be added to the backup before actually running it:

$ restic backup --dry-run --verbose=3 foo.txt
open repository
repository d59c3d15 opened successfully, password is correct
lock repository
load index files
no parent snapshot found, will read all files
start scan on [foo.txt]
start backup on [foo.txt]
scan finished in 8.994s: 1 files, 4 B
new       /foo.txt, saved in 0.003s (0 B added)

Files:           1 new,     0 changed,     0 unmodified
Dirs:            0 new,     0 changed,     0 unmodified
Data Blobs:      0 new
Tree Blobs:      1 new
Would add to the repo: 380 B

processed 1 files, 4 B in 0:09

Comparing two snapshots with restic diff

You can use restic diff to compare the contents of two snapshots. Comparing a snapshot to the previous snapshot can be like a retrospective --dry-run, showing you what a restic backup did.

First use restic snapshots to get the IDs of two snapshots that you want to compare, then pass them to restic diff:

$ restic diff 9eb356c4 b072e1ac
-    foo.txt
+    bar.txt
M    gar.txt

Files:           1 new, 1 removed,     1 changed
Dirs:            0 new, 0 removed
Others:          0 new,    0 removed
Data Blobs:      1 new, 1 removed
Tree Blobs:      1 new, 1 removed
  Added:   384 B
  Removed: 51.780 GiB

In the list of files + denotes a file that was added, - is a file that was removed, M means the file’s contents were modified, U means the file’s metadata was changed and T mean’s the file’s type was changed (for example it changed from a file to a symlink).

Restoring files from your backup

Listing snapshots with restic snapshots

restic snapshots prints out a list of all the snapshots in the repo (each completed restic backup command creates a new snapshot). This is mostly useful for getting snapshot IDs to pass to commands like restic restore (see below).

$ restic snapshots
repository d59c3d15 opened successfully, password is correct
ID        Time                 Host          Tags        Paths
--------------------------------------------------------------------
1f5cfe20  2020-04-28 09:04:47  beatsworking              /home/seanh
81754433  2020-12-24 17:43:24  chamlis                   /home/seanh
27ca05c7  2021-04-29 18:20:00  chamlis                   /home/seanh
f362f638  2021-05-28 14:13:38  chamlis                   /home/seanh
ad4bf831  2021-06-10 18:32:55  chamlis                   /home/seanh
1fa5c822  2021-07-29 17:26:37  chamlis                   /home/seanh
50d3f0c2  2021-08-28 18:10:00  chamlis                   /home/seanh
2a62a5ed  2021-09-23 17:18:26  chamlis                   /home/seanh
9ae436fb  2021-10-28 17:01:38  chamlis                   /home/seanh
09f6ec02  2021-11-25 17:17:52  chamlis                   /home/seanh
053973eb  2021-12-25 19:43:58  chamlis                   /home/seanh
7939dbe7  2022-01-27 19:49:08  chamlis                   /home/seanh
43b899df  2022-02-26 16:55:29  chamlis                   /home/seanh
e377529b  2022-04-01 19:16:42  chamlis                   /home/seanh
9eb356c4  2022-04-01 20:37:36  beatsworking              /home/seanh
22a390d4  2022-04-02 23:41:36  beatsworking              /home/seanh
--------------------------------------------------------------------
16 snapshots

Listing files in snapshots with restic ls

restic ls lists files within snapshots. This is mostly useful for finding files to restore with restic restore (see below).

$ # List all files in the latest snapshot from any host.
$ restic ls latest
repository d59c3d15 opened successfully, password is correct
snapshot 7518e078 of [/home/seanh/foo.txt] filtered by [] at 2022-04-02 22:23:21.053983381 +0100 BST):
/foo.txt

$ # List all files in the latest snapshot from the current host.
$ restic ls --host "$(hostname)" latest
repository d59c3d15 opened successfully, password is correct
snapshot 7518e078 of [/home/seanh/foo.txt] filtered by [] at 2022-04-02 22:23:21.053983381 +0100 BST):
/foo.txt

$ # List all files in the latest snapshot of your home directory from the
$ # current host.
$ restic ls --host "$(hostname)" --path "$HOME" latest
repository d59c3d15 opened successfully, password is correct
snapshot 7518e078 of [/home/seanh/foo.txt] filtered by [] at 2022-04-02 22:23:21.053983381 +0100 BST):
/foo.txt

$ # List all files in a specific snapshot by snapshot ID.
$ restic ls 7518e078
repository d59c3d15 opened successfully, password is correct
snapshot 7518e078 of [/home/seanh/foo.txt] filtered by [] at 2022-04-02 22:23:21.053983381 +0100 BST):
/foo.txt

$ # List all files in the $HOME/Mail directory in the latest snapshot.
$ # When one or more directory arguments are given it only lists top-level files
$ # and folders in the given folder(s) unless you also give --recursive to tell
$ # it to recurse into subfolders.
$ restic ls --recursive latest $HOME/Mail
...

Restoring files from a snapshot with restic restore

Use restic snapshots to find the ID of the snapshot that you want to restore from. (f11c451b in the examples below). Then to restore the entire contents of the snapshot to a /tmp/restored directory:

$ mkdir /tmp/restored
$ restic restore f11c451b --target /tmp/restored

Add --verify to verify the contents of the files after restoring them.

Use --include to restore just a single file or folder from the snapshot. First use restic ls to find the paths of the files you want to restore and then run a restore command like:

$ restic restore f11c451b --target /tmp/restore-work --include /foo.txt

--include can be given multiple times to restore multiple specific files or folders at once and patterns can be used to restore all files that match a pattern. There’s also --exclude to exclude specific or matching files from the restore, restoring everything that isn’t excluded. See Restoring from backup in the restic docs or restic help restore for details.

Restoring from the “latest” snapshot

You can use latest instead of a snapshot ID to restore from the most recent snapshot:

$ restic restore latest --target /tmp/restored

latest on its own means the most recent snapshot from any host and of any path.

If you have multiple hosts backing up to the same repo and you want to restore the latest snapshot from the current host use --host "$(hostname)" with latest.

If you back up multiple paths to the same repo and you want to restore the latest snapshot of your home directory use --path "$HOME" with latest.

Here’s an example combining both:

$ restic restore --host "$(hostname)" --path "$HOME" latest --target /tmp/restored

Maintaining your backup

If your backup is large the restic prune and restic check commands can download a lot of data (potentially gigabytes) because they need to load all your indexes and snapshots. This can take a long time and cost you a lot of money in B2 download fees. For this reason you might not want to run prune and check too frequently. I run the maint script below once a year.

Deleting snapshots with restic forget and restic prune

You can delete old snapshots to save storage space. To delete a specific snapshot or snapshots get the snapshot ID(s) from restic snapshots and pass them to restic forget. For example:

$ restic forget f11c451b bea27147 f7e72151
repository d59c3d15 opened successfully, password is correct
[0:02] 100.00%  3 / 3 files deleted...

Just forgetting snapshots doesn’t actually save any space. You then have to run restic prune to find and delete the data that’s no longer used by any of the remaining snapshots:

$ restic prune
repository d59c3d15 opened successfully, password is correct
loading indexes...
loading all snapshots...
finding data that is still in use for 17 snapshots
[2:37] 100.00%  17 / 17 snapshots...
searching used packs...
collecting packs for deletion and repacking
[1:01] 100.00%  181618 / 181618 packs processed...

to repack:             0 blobs / 0 B
this removes:          0 blobs / 0 B
to delete:             5 blobs / 1.986 KiB
total prune:           5 blobs / 1.986 KiB
remaining:       5877282 blobs / 862.804 GiB
unused size after prune: 16.160 GiB (1.87% of remaining size)

rebuilding index
[7:50] 100.00%  181616 / 181616 packs processed...
deleting obsolete index files
[0:17] 100.00%  120 / 120 files deleted...
removing 2 old packs
[0:00] 100.00%  2 / 2 files deleted...
done

Restic’s docs advise you to run restic check (see below) after doing a restic prune.

To automatically select which snapshots to delete use restic forget with one or more --keep-* options. There are lots of --keep-* options, like --keep-last n to keep the last n snapshots or --keep-hourly n to keep only one snapshot per hour for the last n hours that have snapshots. See Removing snapshots according to a policy in restic’s docs for all the details. Any snapshots that don’t match one of the given --keep-* options will be deleted.

Use --dry-run to see which snapshots would be deleted before actually deleting them.

Here’s an example restic forget command using some of the --keep-* options:

$ restic forget --dry-run \
                --host "$(hostname)" \
                --tag '.restic/backup' \
                --group-by 'host,tags' \
                --keep-last 10 \
                --keep-within-hourly 1d \
                --keep-within-daily 7d \
                --keep-within-weekly 1m \
                --keep-within-monthly 1y \
                --keep-within-yearly 100y

This will keep the 10 most recent snapshots and also hourly snapshots for the last day, daily snapshots for the last week, weekly snapshots for the last month, monthly snapshots for the last year, and yearly snapshots for the last century. “Last day/week/month/year/century” are relative to the date of the latest snapshot, not to the current date.

In addition the --host "$(hostname)" means that it’ll only delete snapshots from the current host and the --tag '.restic/backup' means it’ll only delete snapshots that have the .restic/backup tag (which the backup script above puts on all its snapshots).

All other snapshots will be deleted (if the command is re-run without the --dry-run).

Again, you’d then need to run restic prune to actually free up the space.

The --group-by 'host,tags' matches the --group-by value used in the restic backup command in the backup script above.

In the restic forget command --group-by tells restic how to group snapshots before applying the --keep-* options. The default is --group-by host,paths meaning that if you run (for example) restic forget --keep-last 3 it’ll first group snapshots by host and paths and then will keep the latest three snapshots in each group. This default value is a safety feature to prevent accidentally deleting unrelated snapshots. Removing snapshots according to a policy in the restic docs has all the details.

But the default value means that if you change the paths in your backup script from one snapshot to the next, old snapshots with different paths might never get deleted. So --group-by 'host,tags' tells restic to consider all snapshots from the same host and with the same tags as a single group and apply the --keep-* options to them all at once. We’re already limiting restic forget to snapshots of the current host with --host "$(hostname)" and to snapshots created by our backup script with --tag '.restic/backup' so we know we’re not going to accidentally delete any unrelated snapshots.

Verifying a backup with restic check

The restic check command verifies the integrity of your restic repository:

$ restic check
using temporary cache in /tmp/restic-check-cache-423433534
repository d59c3d15 opened successfully, password is correct
created new cache in /tmp/restic-check-cache-423433534
create exclusive lock for repository
load indexes
check all packs
check snapshots, trees and blobs
[2:02:10] 100.00%  130 / 130 snapshots
no errors were found

restic check just checks the “structural integrity and consistency” of the backup. If you want to check that the actual backed up files are correct you can use restic check --read-data but this will download all the files in the repository which can take a long time and add a lot of money to your Backblaze bill. Alternatively you can use restic check --read-data-subset=1G to check just a random one gigabyte subset of the data:

$ restic check --read-data-subset=1G
using temporary cache in /tmp/restic-check-cache-844156038
repository d59c3d15 opened successfully, password is correct
created new cache in /tmp/restic-check-cache-844156038
create exclusive lock for repository
load indexes
check all packs
check snapshots, trees and blobs
[2:11:01] 100.00%  130 / 130 snapshots
read 1G of data packs
[0:06] 100.00%  1 / 1 packs
no errors were found

restic check with --read-data or --read-data-subset first checks the repo’s structural integrity and consistency (everything that a plain restic check would do) and then downloads and checks the files.

There are other ways to specify the subset to check as well, see Checking integrity and consistency in the restic docs.

Create a maintenance script

To make maintaining your backup easy create a ~/.restic/maint script that runs restic forget with some --keep-* options, then restic prune, then restic check. Here’s an example:

#!/bin/bash
set -euo pipefail

source ~/.restic/env.bash

restic forget \
    --host "$RESTIC_HOST" \
    --tag '.restic/backup' \
    --group-by 'host,tags' \
    --keep-within-daily 7d \
    --keep-within-weekly 1m \
    --keep-within-monthly 1y \
    --keep-within-yearly 100y

restic prune

restic check --read-data-subset=1G

Make the maint script executable:

$ chmod u+x ~/.restic/maint

Now to weed out old snapshots and verify your backup you can just run:

$ ~/.restic/maint

Accessing another host’s backups

Sometimes you might want to access one host’s backups from a different host, for example to recover a different host’s files or to run maintenance commands for another host’s backups. I like to run the maintenance commands for all my backups from my main desktop machine, it saves having to run them on each laptop separately and saves the laptops from being occupied by long running commands.

An easy way to do this is to create a separate env.HOSTNAME.bash file for each host whose backups you want to access:

$ ls ~/.restic/*.bash
/home/seanh/.restic/env.bash
/home/seanh/.restic/env.beatsworking.bash
/home/seanh/.restic/env.xenocrat.bash

The env.bash file is for this host’s backups, the env.beatsworking.bash and env.xenocrat.bash files are for beatsworking and xenocrats backups (the hostnames of two laptops that I back up). If you use fish-shell you’ll want to create env.HOSTNAME.fish files for each host as well.

The contents of each env.HOSTNAME.bash file should be the same as the env.bash file but hard-coding the appropriate hostname and home directory instead of using $(hostname) and $HOME. Here’s an example:

#!/bin/bash
set -euo pipefail

RESTIC_HOST=xenocrat
RESTIC_REPOSITORY="$(pass "$RESTIC_HOST"/RESTIC_REPOSITORY)"
B2_ACCOUNT_ID="$(pass "$RESTIC_HOST"/B2_ACCOUNT_ID)"
B2_ACCOUNT_KEY="$(pass "$RESTIC_HOST"/B2_ACCOUNT_KEY)"
RESTIC_PASSWORD_COMMAND=pass\ "$RESTIC_HOST"/RESTIC_PASSWORD

export RESTIC_HOST RESTIC_REPOSITORY B2_ACCOUNT_ID B2_ACCOUNT_KEY RESTIC_PASSWORD_COMMAND

For the env.HOSTNAME.bash scripts to work you’ll need to add a RESTIC_REPOSITORY, B2_ACCOUNT_ID, B2_ACCOUNT_KEY and RESTIC_PASSWORD for each host for the password store:

$ pass ls
Password Store
├── beatsworking
│   ├── B2_ACCOUNT_ID
│   ├── B2_ACCOUNT_KEY
│   ├── RESTIC_PASSWORD
│   └── RESTIC_REPOSITORY
├── chamlis
│   ├── B2_ACCOUNT_ID
│   ├── B2_ACCOUNT_KEY
│   ├── RESTIC_PASSWORD
│   └── RESTIC_REPOSITORY
└── xenocrat
    ├── B2_ACCOUNT_ID
    ├── B2_ACCOUNT_KEY
    ├── RESTIC_PASSWORD
    └── RESTIC_REPOSITORY

When I do this I create a new application key in Backblaze and add a second RESTIC_PASSWORD to the repo using the restic key command (remembering to back up the RESTIC_PASSWORD in Bitwarden) rather than copying the same key and password to two machines. This just means that each machine’s key and password can be revoked separately.

Now you can easily move between backups by just sourcing the env files and then running restic commands:

$ source ~/.restic/env.bash
$ restic check
...
$ source ~/.restic/env.beatsworking.bash
$ restic check
...
$ source ~/.restic/env.xenocrat.bash
$ restic check
...

I extended my annual maintenance script to source each env file in turn and run my maintenance commands on each repo:

#!/bin/bash
set -euo pipefail

function maint {
    # Print this so you can tell which backup restic's output belongs to.
    echo "maint $1"

    source "$1"

    restic forget \
        --host "$RESTIC_HOST" \
        --tag '.restic/backup' \
        --group-by 'host,tags' \
        --keep-within-daily 7d \
        --keep-within-weekly 1m \
        --keep-within-monthly 1y \
        --keep-within-yearly 100y

    restic prune

    restic check --read-data-subset=1G
}

for env_file in ~/.restic/env*.bash; do
    maint "$env_file"
done

restic’s built-in help

restic has built-in reference documentation for all its subcommands and options. For a list of restic’s subcommands and global flags run restic help or restic --help:

$ restic help

For documentation of a subcommand and its options run restic help <SUBCOMMAND> or restic <SUBCOMMAND> --help, for example:

$ restic help backup

Troubleshooting

Please specify repository location” error

If you get this error:

Fatal: Please specify repository location (-r or --repository-file)

It probably means you forgot to source your env file. Run:

$ source ~/.restic/env.bash

And then re-run your original command.

unable to create lock in backend” error

If you get this error:

unable to create lock in backend: repository is already locked exclusively by PID ...
...
lock was created at ...
storage ID ...
the `unlock` command can be used to remove stale locks

Just run the restic unlock command, as the error message says:

$ restic unlock

And then re-run your original command.

Stale locks can happen if a restic command crashes or if a computer that was running a restic command crashes or loses power etc.

TODO

restic find

There’s a restic find command for finding files that match a pattern, searching across all snapshots at once which could be useful. It’s poorly documented and didn’t seem to behave as I expected it to. Just grepping the output of restic ls can probably get you pretty far.

restic mount

There’s a restic mount command for mounting your snapshots as a browseable FUSE filesystem. You can restore files by just copying them normally.

$ mkdir /tmp/restic
$ restic mount /tmp/restic
repository d59c3d15 opened successfully, password is correct
Now serving the repository at /tmp/restic/
Use another terminal or tool to browse the contents of this folder.
When finished, quit with Ctrl-c here or umount the mountpoint.

I found restic mount to be buggy when I tried it. It worked once, but after that the mounted filesystem kept appearing to be empty.

What if restic check finds a problem?

What should you do if restic check reports a problem with your backup? I’ve never had this happen so I don’t know how to handle it. Will restic offer to fix the problem or suggest a solution? Should you create a new repo and start again from scratch?