Backblaze B2 is low-cost object storage with an S3-compatible API, which makes it a common destination for off-site server backups. restic is a backup program that encrypts data on the server, deduplicates it and only uploads what changed, so daily backups stay small and the storage provider never sees your files. In this tutorial you will create a private B2 bucket and a restricted key, initialize a restic repository in it from Ubuntu 24.04, schedule daily backups with retention using a systemd timer, and restore files to prove it works.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user with sudo privileges.
  • A Backblaze account with B2 Cloud Storage enabled. The first 10 GB of storage are free, which is enough to follow along.
  • A list of the directories you want to protect. The examples back up /etc, /home and /var/www.

Step 1 - Creating a bucket and an application key

In the Backblaze web interface:

  1. Go to Buckets and create a bucket with a globally unique name, such as your_bucket_name. Set the files to Private.
  2. Note the bucket's Endpoint shown in the bucket list, for example s3.us-west-004.backblazeb2.com. The region part depends on where your account was created.
  3. Open the bucket's Lifecycle Settings and choose Keep only the last version of the file. B2 keeps old versions of every file by default; when restic deletes data during pruning, this setting makes sure the space is actually freed.
  4. Go to Application Keys and add a new key. Restrict it to your_bucket_name with Read and Write access.
  5. Copy the keyID and applicationKey. The application key is shown only once.

Step 2 - Installing restic

restic is packaged in the Ubuntu archive:

sudo apt update
sudo apt install restic

Check the version:

restic version
restic 0.16.4 compiled with go1.22.2 on linux/amd64

Step 3 - Storing the credentials and repository password

restic needs three things: the B2 key, the repository location and a password that encrypts the repository. Keep them in root-only files under /etc/restic so the scheduled job and your manual commands use the same settings.

Create the directory:

sudo install -d -m 0700 /etc/restic

Generate a strong random repository password:

openssl rand -base64 32 | sudo tee /etc/restic/password > /dev/null
sudo chmod 600 /etc/restic/password

Create the environment file:

sudo nano /etc/restic/b2.env
AWS_ACCESS_KEY_ID=your_key_id
AWS_SECRET_ACCESS_KEY=your_application_key
RESTIC_REPOSITORY=s3:https://s3.us-west-004.backblazeb2.com/your_bucket_name/restic
RESTIC_PASSWORD_FILE=/etc/restic/password

Replace the endpoint with the one from Step 1. The /restic suffix places the repository in a folder inside the bucket. restic's documentation recommends B2's S3-compatible API over its native b2: backend, which is why the variables use the AWS names.

Protect the file:

sudo chmod 600 /etc/restic/b2.env

Step 4 - Initializing the repository

Open a root shell and load the variables into it. You will use the same pattern whenever you run restic by hand:

sudo -i
set -a; source /etc/restic/b2.env; set +a

Initialize the repository:

restic init
created restic repository 4f1c2e9d3a at s3:https://s3.us-west-004.backblazeb2.com/your_bucket_name/restic

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

Step 5 - Running the first backup

Still in the root shell, back up the directories you care about. --exclude-caches skips directories marked as caches, and --one-file-system avoids crossing into other mounts:

restic backup --one-file-system --exclude-caches /etc /home /var/www
repository 4f1c2e9d opened (version 2, compression level auto)
created new cache in /root/.cache/restic
...
Files:        4210 new,     0 changed,     0 unmodified
Dirs:          612 new,     0 changed,     0 unmodified
Added to the repository: 188.412 MiB (61.022 MiB stored)

processed 4210 files, 241.886 MiB in 0:19
snapshot 9b3c1f8e saved

Run the same command again. Only changed data is uploaded, so the second run is much faster:

restic backup --one-file-system --exclude-caches /etc /home /var/www

List the snapshots:

restic snapshots
ID        Time                 Host   Tags  Paths
------------------------------------------------------------
9b3c1f8e  2026-09-25 10:31:02  web01        /etc
                                            /home
                                            /var/www
d47a0c21  2026-09-25 10:32:40  web01        /etc
                                            /home
                                            /var/www
------------------------------------------------------------
2 snapshots

If you run MySQL or MariaDB, back up a consistent dump as well. restic can read it from standard input without writing a temporary file:

mysqldump --all-databases --single-transaction | restic backup --stdin --stdin-filename all-databases.sql

Type exit to leave the root shell when you are done.

Step 6 - Scheduling backups and retention with systemd

A systemd service runs the backup and then applies the retention policy, and a timer triggers it every night. Since systemd reads the same environment file, no extra script is needed.

Create the service:

sudo nano /etc/systemd/system/restic-backup.service
[Unit]
Description=restic backup to Backblaze B2
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/b2.env
Environment=HOME=/root
Nice=10
IOSchedulingClass=idle
ExecStart=/usr/bin/restic backup --one-file-system --exclude-caches /etc /home /var/www
ExecStartPost=/usr/bin/restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6

ExecStartPost only runs if the backup succeeded, so a failed backup never triggers pruning. The forget policy keeps the last 7 daily, 4 weekly and 6 monthly snapshots and deletes the data no longer referenced by any of them. Adjust the paths in ExecStart to your server; restic exits with an error if a listed path does not exist.

Create the timer:

sudo nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Nightly restic backup to Backblaze B2

[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=20m
Persistent=true

[Install]
WantedBy=timers.target

Reload systemd and enable the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer

Trigger one run now to test the service instead of waiting for the night:

sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service -n 20

The journal should end with a snapshot ... saved line followed by the forget output listing the snapshots kept. Check the next scheduled run:

systemctl list-timers restic-backup.timer

Step 7 - Verifying and restoring

A backup is only useful if it restores. Open a root shell with the variables loaded, as in Step 4:

sudo -i
set -a; source /etc/restic/b2.env; set +a

Check the repository structure, and read back a random 5% of the stored data to detect corruption:

restic check --read-data-subset=5%
...
no errors were found

Restore a single directory from the latest snapshot into a scratch location:

restic restore latest --target /tmp/restore --include /etc/ssh

Compare it with the live files:

diff -r /etc/ssh /tmp/restore/etc/ssh && echo "restore OK"
restore OK

To restore everything, drop --include. To pick files interactively, mount the repository with FUSE (install the fuse3 package first) and browse the snapshots directory:

mkdir -p /mnt/restic
restic mount /mnt/restic

Press CTRL+C to unmount. Remove /tmp/restore and type exit when you are finished.

Troubleshooting

Fatal: unable to open config file or Access Denied. The key does not have access to the bucket, or the endpoint or bucket name in RESTIC_REPOSITORY is wrong. Confirm the key is restricted to the right bucket with read and write access, and that the endpoint matches the one shown for the bucket.

Fatal: wrong password or no key found. RESTIC_PASSWORD_FILE points to a different password from the one used at restic init. Restore the original password from your safe copy.

unable to create lock in backend: repository is already locked. A previous run was interrupted. Make sure no restic process is running (pgrep restic), then remove stale locks with restic unlock.

Bucket size keeps growing after pruning. The bucket still keeps old file versions. Set its lifecycle to Keep only the last version of the file as in Step 1; B2 applies it within a day.

Conclusion

Your server now sends encrypted, deduplicated backups to Backblaze B2 every night, keeps a sensible history of snapshots and frees space automatically, and you have confirmed that files come back intact.

As next steps, schedule a monthly restic check --read-data-subset run, alert when the newest snapshot is older than a day, and consider enabling Object Lock on a separate bucket if you need backups that cannot be deleted even with valid credentials.