Quick answer

A systemd timer fires restic backup to Backblaze B2 nightly at 03:00 with RandomizedDelaySec=900, then forget –keep-daily 7 –keep-weekly 4 –keep-monthly 6 prunes to at most 17 snapshots. Storage runs $0.00695/GB/month, about $1.39 for 200GB. Credentials sit in /etc/restic/b2.env at chmod 600; lose RESTIC_PASSWORD and the repository is unrecoverable. A healthchecks.io ping surfaces silent failures.

By LK Wood IV · 2026-06-04 · ~13 min read · St. Louis County, MO

Architecture diagram of the restic to Backblaze B2 off-site backup pipeline: a systemd timer fires at 3am with a 15-minute randomized delay, restic reads /opt/stacks, /etc and /home, encrypts and deduplicates client-side, uploads over HTTPS to a private B2 bucket, then applies a 7-daily / 4-weekly / 6-monthly retention policy (max 17 snapshots) and pings Uptime Kuma or healthchecks.io on success. Forms copy 3 of a 3-2-1 stack at about $1.40/month for 200GB.

A backup that doesn’t run automatically doesn’t run. A backup that isn’t off-site doesn’t protect you from physical disasters. A backup you haven’t tested restoring from might not actually work. This guide sets up restic — the best homelab backup tool available — with Backblaze B2 as the off-site destination, automated scheduling via systemd, and monitoring so you know when backups fail.

Why restic

restic is a fast, encrypted, deduplicated backup tool written in Go. It:

  • Encrypts everything client-side before upload — Backblaze can’t read your data
  • Deduplicates at the chunk level — backing up files that haven’t changed costs nothing after the first backup
  • Handles incremental backups natively — no need to manage “full” vs “differential” schedules
  • Compresses since v0.14.0 — the old “restic has no compression” line is years out of date

If you’re still choosing a tool rather than deploying one, restic vs Borg vs Kopia decides it by where your backup lands — restic and Kopia write straight into a bucket, Borg needs its own binary at the far end.

  • Supports many backends — B2, S3, SFTP, Wasabi, local filesystem, and more
  • Has no backup agent required on the destination — just the restic binary on your machine and credentials to the remote

Step 1: Install restic

# Debian/Ubuntu
apt install -y restic

# Or download the binary directly. restic's release assets are version-stamped,
# so there is no "latest" filename — check github.com/restic/restic/releases and
# substitute the current version for 0.19.1 below.
wget https://github.com/restic/restic/releases/download/v0.19.1/restic_0.19.1_linux_amd64.bz2
bzip2 -d restic_0.19.1_linux_amd64.bz2
mv restic_0.19.1_linux_amd64 /usr/local/bin/restic
chmod +x /usr/local/bin/restic

# Verify
restic version

Step 2: Set up Backblaze B2

  1. Create a Backblaze account at backblaze.com
  2. Create a bucket: Buckets → Create a Bucket
    • Bucket name: homelab-restic-backups-yourname (must be globally unique)
    • Files in Bucket: Private
    • Default Encryption: Enabled (optional — restic encrypts anyway, but defense in depth)
    • Object Lock: Optional — enables immutable storage, protects against ransomware deleting backups
  3. Create an Application Key: App Keys → Add a New Application Key
    • Name: restic-homelab
    • Allow access to: only the bucket you created
    • Type of Access: Read and Write
    • Note the keyID and applicationKey — they’re shown once

Step 3: Create the environment file

Store your credentials in a protected file — not in scripts or cron commands:

mkdir -p /etc/restic
chmod 700 /etc/restic

cat > /etc/restic/b2.env << 'EOF'
B2_ACCOUNT_ID=your-keyID-here
B2_ACCOUNT_KEY=your-applicationKey-here
RESTIC_REPOSITORY=b2:homelab-restic-backups-yourname:/
RESTIC_PASSWORD=your-strong-backup-password-here
EOF

chmod 600 /etc/restic/b2.env

The repository password (RESTIC_PASSWORD) encrypts all your backup data. If you lose it, your backup data is permanently inaccessible. Store this password:

  • In your password manager (Vaultwarden/Bitwarden)
  • In a physical printout stored somewhere safe
  • NOT only on the machine you’re backing up

Step 4: Initialize the repository

source /etc/restic/b2.env
restic init

You should see:

created restic repository abc123def at b2:homelab-restic-backups-yourname:/

This creates the repository structure in your B2 bucket. It stores encrypted keys and an index in the bucket root.

Step 5: First backup

Test with a manual backup of a small directory first:

source /etc/restic/b2.env

# Back up a single directory
restic backup /opt/stacks/vaultwarden/data

# Check what was backed up
restic snapshots

You should see a snapshot entry with the date, hostname, and paths backed up.

For a homelab, back up:

  • Docker service data directories: /opt/stacks/*/data
  • Configuration files: /etc/
  • Home directories: /home/
  • Anything in /opt/ you care about
restic backup \
  /opt/stacks \
  /etc \
  /home \
  --exclude "*.tmp" \
  --exclude "**/cache/**" \
  --exclude "**/node_modules/**"

The --exclude flags keep temporary files out of the backup. Add more excludes for large directories you don’t need to back up (video files, ISO images).

Step 6: Automate with systemd

Create two files: a service (what to run) and a timer (when to run it).

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

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/b2.env
ExecStart=/usr/bin/restic backup \
  /opt/stacks \
  /etc \
  /home \
  --exclude "*.tmp" \
  --exclude "**/cache/**" \
  --exclude "**/node_modules/**" \
  --verbose
ExecStartPost=/usr/bin/restic forget \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 6 \
  --prune
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Run Restic backup daily at 3am

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=900

[Install]
WantedBy=timers.target

The RandomizedDelaySec=900 adds a random 0–15 minute delay to the scheduled time. This prevents all your nodes from hammering B2 at exactly 3:00:00am simultaneously.

Enable and start:

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

# Verify timer is active
systemctl list-timers restic-backup.timer

Run it manually to confirm it works:

systemctl start restic-backup.service
journalctl -u restic-backup.service -f

Step 7: Retention policy

The restic forget --prune command in the service removes old snapshots based on your retention policy. The settings above keep:

--keep-daily 7    → one snapshot per day for the last 7 days
--keep-weekly 4   → one per week for the last 4 weeks
--keep-monthly 6  → one per month for the last 6 months

This gives you granular recovery for recent data and longer-term recovery points. After 6 months, only one snapshot per month is kept.

Total snapshots over 6 months: at most 7 + 4 + 6 = 17 snapshots. Because of deduplication, the actual storage cost depends on how much data changed between backups — unchanged files across all 17 snapshots are stored only once.

Adjust the retention to match your needs and your B2 storage budget.

Step 8: Monitor — know when backups fail

Silent backup failures are more dangerous than no backup at all — they create false confidence.

Option A: Uptime Kuma push monitor. Add this to the end of your systemd service to ping Uptime Kuma on success:

ExecStartPost=/usr/bin/curl -fsS \
  "https://uptime.yourdomain.com/api/push/YOUR-PUSH-KEY?status=up&msg=restic+OK"

If the service fails before reaching ExecStartPost, or if the curl itself fails, Uptime Kuma doesn’t receive the push and alerts you.

Option B: healthchecks.io (free tier). healthchecks.io is a service designed specifically for this: you ping a URL after each successful backup, and it alerts you if the ping stops coming. The free tier covers 20 checks.

# Add to ExecStartPost:
/usr/bin/curl -fsS https://hc-ping.com/YOUR-UUID

Check backup status manually:

source /etc/restic/b2.env
restic snapshots    # list all snapshots
restic check        # verify repository integrity

Run restic check weekly. It downloads metadata and verifies chunk hashes — it will catch a corrupt repository before you need a restore.

Step 9: Test a restore

Do this before you need it:

source /etc/restic/b2.env

# List snapshots
restic snapshots

# Restore a specific file to a temp directory
restic restore latest \
  --target /tmp/restic-test-restore \
  --include /opt/stacks/vaultwarden/data/db.sqlite3

# Verify the file is there and readable
ls -la /tmp/restic-test-restore/opt/stacks/vaultwarden/data/
sqlite3 /tmp/restic-test-restore/opt/stacks/vaultwarden/data/db.sqlite3 ".tables"

Do a full directory restore to verify your most important data at least monthly.

Advanced: backing up Proxmox PBS datastores

If you’re running Proxmox Backup Server for VM/LXC backups, you can use restic to push PBS datastore contents to B2 as the off-site layer:

# /etc/systemd/system/restic-pbs.service
[Unit]
Description=Restic backup of PBS datastore to B2
After=pve-storage-local.mount

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/b2-pbs.env
ExecStart=/usr/bin/restic backup /mnt/backup-drive/pve-backups \
  --tag pbs-datastore
ExecStartPost=/usr/bin/restic forget \
  --tag pbs-datastore \
  --keep-daily 3 --keep-weekly 2

This gives you a 3-2-1 stack:

  • Copy 1: live Proxmox VMs on the production host
  • Copy 2: PBS local backup
  • Copy 3: restic → B2 (off-site, encrypted)

Bandwidth and cost estimate

For a homelab with 200GB of unique backup data, after the initial upload:

  • Initial B2 upload: 200GB × $0 (B2 doesn’t charge upload bandwidth) = $0
  • Monthly storage: 200GB × $0.00695 = $1.39/month
  • Monthly incremental upload: if 5GB changes per day × 30 days = 150GB new data, but after deduplication maybe 3GB/month net new = ~$0.02/month extra
  • Total B2 cost: ~$1.40–1.70/month

For 100GB of backup data: ~$0.70/month. Off-site encrypted backup for less than a cup of coffee. The bill from B2 is the easy part — the other cost is your own connection, so if you are on a metered plan, weigh that first seed upload against your household’s normal monthly volume with the data usage calculator.


The Proxmox Backup Server guide covers the PBS layer (copy 2 in the 3-2-1 stack). This guide covers copy 3. Together they complete a full 3-2-1 backup strategy — see that page for what the three copies are and why RAID isn’t one of them.

Sources

Frequently asked questions

Why use restic instead of rclone, borgbackup, or duplicati?
Restic encrypts all data client-side before upload, deduplicates at the chunk level (so backing up the same unchanged files costs almost nothing after the initial upload), and handles incremental backups natively. rclone syncs files but doesn’t do deduplication. borgbackup is similar to restic but less friendly on Windows and lacks restic’s native B2 support. Duplicati has a history of backup corruption bugs that the community has been burned by repeatedly.
How much does Backblaze B2 cost?
Backblaze B2 charges $0.00695/GB/month ($6.95/TB) for storage and $0.01/GB for downloads beyond the free allowance. For a typical homelab with 100GB of backup data after deduplication, that’s about $0.70/month storage. Uploads are free, and B2 includes free egress up to 3× your average monthly stored data, so routine restores rarely cost anything. The first 10GB of storage is free.
Is restic backup data readable without restic?
No. Restic stores data as encrypted, deduplicated chunks. You need the restic binary and the repository password to read or restore from a restic repository. The encryption protects your data even if your B2 bucket is accessed by someone else — they can’t read the contents without the password.
How do I restore a single file from a restic backup?
Use ‘restic restore –target /restore-path –include /path/to/file SNAPSHOT_ID’ — this restores only the specified file rather than the entire snapshot. ‘restic ls latest’ lists the contents of the most recent snapshot so you can find the exact path.
Can I back up a running database with restic?
You can, but you risk backing up an inconsistent state if the database is writing during the snapshot. Best practice: either stop the database briefly during backup, use the database’s own dump tool first (pg_dump, mysqldump, sqlite3 .backup) and back up the dump file, or ensure the database uses write-ahead logging (WAL) that provides crash-consistent snapshots.

Evidence ledger

Last updated
Methodology
This tutorial was written and edited by Lowell K. Wood IV in St. Louis County, MO. Specs, prices, commands, and version numbers are drawn from the official vendor, reseller, and project documentation current on the date above, and were verified before publishing. First-person hardware claims appear only where the article shows a verifiable artifact — a photo, receipt, or measurement — or links to the TechFuelHQ Open Bench Datasets. Every fact is human-verified against its cited source before publishing; AI assists with first-draft structure and source-gathering, not with the verdict. Full editorial standard: methodology.
Update log
  • 2026-07-25 — Last reviewed and updated.
Corrections
Spotted an error or stale price? Email hello@techfuelhq.com. Confirmed corrections are added to the update log above.

About the author

Written by Lowell K. Wood IV. Lowell builds and runs TechFuelHQ from St. Louis, Missouri, pairing thirteen-plus years of hands-on homelab, PC, server, and networking experience with cited third-party testing and first-party benchmarks on the gear he still runs. He also works ground EMS as a Nationally Registered Paramedic (NREMT).