Automated Off-Site Backups with restic
By LK Wood IV · 2026-06-13 · ~13 min read · St. Louis County, MO
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
- 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 latest binary directly
wget https://github.com/restic/restic/releases/latest/download/restic_linux_amd64.bz2
bzip2 -d restic_linux_amd64.bz2
mv restic_linux_amd64 /usr/local/bin/restic
chmod +x /usr/local/bin/restic
# Verify
restic version
Step 2: Set up Backblaze B2
- Create a Backblaze account at backblaze.com
- 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
- Bucket name:
- 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
keyIDandapplicationKey— they’re shown once
- Name:
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.006 = $1.20/month
- Monthly incremental upload: if 5GB changes per day × 30 days = 150GB new data, but after deduplication maybe 3GB/month net new = $0.018/month extra
- Total B2 cost: ~$1.20–1.50/month
For 100GB of backup data: ~$0.60/month. Off-site encrypted backup for less than a cup of coffee.
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.