Setup time: ~45 minutes with Claude Code assistance (not counting initial backup upload, which runs in the background and may take hours/days depending on your data size and internet speed).

A practical guide to backing up your computer to the cloud using restic and Backblaze B2. Your files are encrypted before leaving your machine, deduplicated to save space, and stored offsite for ~$6/TB/month.

Why This Setup?

  • Encrypted: Your data is encrypted with a password before upload. Backblaze can’t read it.
  • Deduplicated: Only changed portions of files are uploaded. A 10GB file with 1MB of changes = 1MB upload.
  • Versioned: Keep 30 days of snapshots. Accidentally delete something? Restore from yesterday.
  • Cheap: ~$6/TB/month. 100GB costs ~60 cents/month.
  • Cross-platform: Same tool works on Windows, macOS, and Linux.

What about OneDrive/iCloud/Google Drive?

Those sync services are great for access across devices, but they’re not proper backups:

  • Delete a file? It’s deleted everywhere.
  • Ransomware encrypts your files? Synced everywhere.
  • They don’t keep long version histories.

Use restic + B2 in addition to your sync service, not instead of it.


Overview

  1. Create a Backblaze B2 account and bucket
  2. Install restic
  3. Configure what to back up (and what to skip)
  4. Run your first backup
  5. Automate daily backups

Pick your OS: Windows 11 | macOS | Linux


Part 1: Backblaze B2 Setup (All Platforms)

1.1 Create Account

  1. Go to backblaze.com/b2
  2. Sign up for B2 Cloud Storage (not “Personal Backup” โ€” that’s a different product)
  3. Verify your email

1.2 Create Bucket

  1. Buckets โ†’ Create a Bucket

  2. Settings:

    • Bucket Name: mybackup-abc123 (must be globally unique โ€” add random characters)
    • Files in Bucket: Private
    • Default Encryption: Disable (restic handles encryption client-side)
    • Object Lock: Disable
  3. Note the S3 Endpoint shown (e.g., s3.us-west-004.backblazeb2.com)

1.3 Set Lifecycle Policy

  1. Click your bucket โ†’ Lifecycle Settings
  2. Set to: “Keep only the last version of the file”

Why this matters: When using B2’s S3-compatible API, restic “hides” old file versions rather than deleting them. B2 keeps hidden versions indefinitely by default, which wastes storage. This lifecycle rule automatically deletes hidden versions after one day, freeing up space. (Restic handles its own versioning via snapshots โ€” B2’s file versioning is redundant.)

1.4 Create Application Key (S3-Compatible)

We need S3-compatible credentials. See Backblaze’s S3-compatible API documentation for details.

  1. App Keys โ†’ Add a New Application Key

  2. Settings:

    • Name: restic-backup
    • Allow access to Bucket(s): Select your specific bucket
    • Type of Access: Read and Write
    • Allow List All Bucket Names: Unchecked
  3. SAVE THESE IMMEDIATELY (shown only once):

    • keyID โ€” this becomes your AWS_ACCESS_KEY_ID
    • applicationKey โ€” this becomes your AWS_SECRET_ACCESS_KEY

Store these in your password manager. You’ll use them with AWS environment variable names (that’s how S3-compatible APIs work).

Why S3-Compatible Mode?

Backblaze B2 offers two connection methods: native B2 (b2:bucket) and S3-compatible (s3:endpoint/bucket). This guide uses S3-compatible mode because restic’s documentation recommends it:

“Due to issues with error handling in the current B2 library that restic uses, the recommended way to utilize Backblaze B2 is by using its S3-compatible API.”

The S3 mode has better error handling and is more reliable for automated backups.


Windows 11

2.1 Install Restic

Option A: Scoop

# Install Scoop if you don't have it
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

# Install restic
scoop install restic

Option B: WinGet (recommended for system-wide backups)

winget install --exact --id restic.restic --scope Machine

This installs to %ProgramFiles% and adds restic to your PATH.

Option C: Manual download

  1. Download from github.com/restic/restic/releases
  2. Get the restic_X.X.X_windows_amd64.zip file
  3. Extract restic.exe to C:\Program Files\restic\
  4. Add C:\Program Files\restic\ to your PATH

2.2 Create Configuration

Create a folder for your backup config:

mkdir "$env:USERPROFILE\.config\restic"

Create the environment file %USERPROFILE%\.config\restic\env.ps1:

$env:RESTIC_REPOSITORY = "s3:s3.us-west-004.backblazeb2.com/YOUR-BUCKET-NAME"
$env:RESTIC_PASSWORD = "YOUR_SECURE_PASSWORD_HERE"
$env:AWS_ACCESS_KEY_ID = "YOUR_B2_KEY_ID"
$env:AWS_SECRET_ACCESS_KEY = "YOUR_B2_APPLICATION_KEY"

Replace:

  • us-west-004 with your region from the bucket page
  • YOUR-BUCKET-NAME with your actual bucket name
  • YOUR_SECURE_PASSWORD_HERE with a strong password (this encrypts your backups โ€” save it in your password manager)
  • The AWS variables with your B2 credentials

2.3 Create Exclude File

Create %USERPROFILE%\.config\restic\excludes.txt:

# Windows system files
$RECYCLE.BIN
System Volume Information
pagefile.sys
hiberfil.sys
swapfile.sys
*.tmp
*.temp
~$*

# Caches
AppData\Local\Temp
AppData\Local\Microsoft\Windows\INetCache
AppData\Local\Microsoft\Windows\Explorer\thumbcache*
AppData\Local\Packages\*\LocalCache
AppData\Local\Packages\*\TempState

# Browser caches (bookmarks/passwords ARE backed up)
AppData\Local\Google\Chrome\User Data\*\Cache
AppData\Local\Google\Chrome\User Data\*\Code Cache
AppData\Local\Google\Chrome\User Data\*\GPUCache
AppData\Local\BraveSoftware\Brave-Browser\User Data\*\Cache
AppData\Local\BraveSoftware\Brave-Browser\User Data\*\Code Cache
AppData\Local\Microsoft\Edge\User Data\*\Cache
AppData\Local\Mozilla\Firefox\Profiles\*\cache2

# Development
node_modules
.venv
__pycache__
*.pyc

# Large app caches
AppData\Local\Steam\steamapps
AppData\Local\Discord\Cache
AppData\Roaming\Spotify\Data

# OneDrive (already in cloud)
OneDrive

2.4 Initialise Repository

Open PowerShell:

# Load environment
. "$env:USERPROFILE\.config\restic\env.ps1"

# Initialise (one-time setup)
restic init

You’ll see a repository ID โ€” save this alongside your password.

2.5 First Backup

Recommended: Exclude large files

Add --exclude-larger-than 300M to skip files over 300MB. Large files (videos, ISOs, game assets) are often replaceable and expensive to store in the cloud. Adjust the threshold to your needs.

restic backup $env:USERPROFILE --exclude-file="$env:USERPROFILE\.config\restic\excludes.txt" --exclude-larger-than 300M --verbose

This single flag can dramatically reduce your backup size and B2 costs.

Optional: Test with a dry run first

. "$env:USERPROFILE\.config\restic\env.ps1"
restic backup $env:USERPROFILE --exclude-file="$env:USERPROFILE\.config\restic\excludes.txt" --dry-run -vv

This shows what would be backed up without uploading anything.

Run the actual backup:

# Load environment
. "$env:USERPROFILE\.config\restic\env.ps1"

# Back up your user folder
restic backup $env:USERPROFILE --exclude-file="$env:USERPROFILE\.config\restic\excludes.txt" --verbose

First backup may take hours depending on data size and upload speed. Subsequent backups only upload changes.

2.6 Automate with Task Scheduler

Create the backup script %USERPROFILE%\.config\restic\backup.ps1:

# Load environment
. "$env:USERPROFILE\.config\restic\env.ps1"

# Run backup (skip files >300MB)
restic backup $env:USERPROFILE --exclude-file="$env:USERPROFILE\.config\restic\excludes.txt" --exclude-larger-than 300M --quiet

# Clean up old snapshots (keep 7 daily, 4 weekly, 6 monthly)
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

Create the scheduled task:

  1. Open Task Scheduler (search in Start menu)
  2. Click Create Task (not “Create Basic Task”)
  3. General tab:
    • Name: Restic Backup
    • Check “Run whether user is logged on or not”
    • Check “Run with highest privileges”
  4. Triggers tab โ†’ New:
    • Begin the task: On a schedule
    • Daily, at 2:00 PM (or whenever your computer is usually on)
    • Check “Enabled”
  5. Actions tab โ†’ New:
    • Action: Start a program
    • Program: powershell.exe
    • Arguments: -ExecutionPolicy Bypass -File "%USERPROFILE%\.config\restic\backup.ps1"
  6. Conditions tab:
    • Uncheck “Start only if the computer is on AC power” (if you want laptop backups on battery)
  7. Settings tab:
    • Check “Run task as soon as possible after a scheduled start is missed”
  8. Click OK, enter your Windows password

2.7 Verify It’s Working

. "$env:USERPROFILE\.config\restic\env.ps1"
restic snapshots

You should see your backup snapshots listed.


macOS

3.1 Install Restic

Option A: Homebrew (recommended)

brew install restic

Option B: Manual download

# Download latest release
curl -LO https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_darwin_arm64.bz2

# Extract (use darwin_amd64 for Intel Macs)
bunzip2 restic_*.bz2
chmod +x restic_*
sudo mv restic_* /usr/local/bin/restic

3.2 Create Configuration

mkdir -p ~/.config/restic

Create ~/.config/restic/env.sh:

export RESTIC_REPOSITORY="s3:s3.us-west-004.backblazeb2.com/YOUR-BUCKET-NAME"
export RESTIC_PASSWORD="YOUR_SECURE_PASSWORD_HERE"
export AWS_ACCESS_KEY_ID="YOUR_B2_KEY_ID"
export AWS_SECRET_ACCESS_KEY="YOUR_B2_APPLICATION_KEY"

Secure the file:

chmod 600 ~/.config/restic/env.sh

Replace the placeholder values with your actual credentials.

3.3 Create Exclude File

Create ~/.config/restic/excludes.txt:

# macOS system
.Trash
.Spotlight-V100
.fseventsd
.DS_Store
.AppleDouble
.LSOverride
._*

# Caches
Library/Caches
Library/Logs
Library/Application Support/Google/Chrome/*/Cache
Library/Application Support/Google/Chrome/*/Code Cache
Library/Application Support/BraveSoftware/Brave-Browser/*/Cache
Library/Application Support/BraveSoftware/Brave-Browser/*/Code Cache
Library/Application Support/Firefox/Profiles/*/cache2
Library/Application Support/Slack/Cache
Library/Application Support/Spotify/PersistentCache
Library/Application Support/discord/Cache
Library/Containers/*/Data/Library/Caches

# Development
node_modules
.venv
__pycache__
*.pyc
.npm
.cargo

# Large/replaceable
Downloads/*.dmg
Downloads/*.iso
Movies
Music/Music/Media
*.photoslibrary

# iCloud (already in cloud)
Library/Mobile Documents

3.4 Initialise Repository

source ~/.config/restic/env.sh
restic init

Save the repository ID shown.

3.5 First Backup

Recommended: Exclude large files

Add --exclude-larger-than 300M to skip files over 300MB. Large files (videos, ISOs, game assets) are often replaceable and expensive to store in the cloud.

restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --verbose

This single flag can dramatically reduce your backup size and B2 costs.

Optional: Test with a dry run first

source ~/.config/restic/env.sh
restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --dry-run -vv

This shows what would be backed up without uploading anything.

Run the actual backup:

source ~/.config/restic/env.sh
restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --verbose

3.6 Automate with launchd

Create the backup script ~/.config/restic/backup.sh:

#!/bin/bash
source ~/.config/restic/env.sh
# Run backup (skip files >300MB)
restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --quiet
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

Make it executable:

chmod +x ~/.config/restic/backup.sh

Create the launchd plist ~/Library/LaunchAgents/com.restic.backup.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.restic.backup</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>$HOME/.config/restic/backup.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>14</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <key>StandardOutPath</key>
    <string>/tmp/restic-backup.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/restic-backup.log</string>
</dict>
</plist>

Note: Replace $HOME with your actual home path (e.g., /Users/yourname). launchd doesn’t expand variables in plists.

Load it:

launchctl load ~/Library/LaunchAgents/com.restic.backup.plist

3.7 Grant Full Disk Access

For restic to back up all your files, grant Terminal (or your terminal app) Full Disk Access:

  1. System Settings โ†’ Privacy & Security โ†’ Full Disk Access
  2. Click +, add Terminal (or iTerm, etc.)
  3. You may need to restart Terminal

3.8 Verify It’s Working

source ~/.config/restic/env.sh
restic snapshots

Linux

4.1 Install Restic

Debian/Ubuntu:

sudo apt update && sudo apt install restic

Fedora:

sudo dnf install restic

Arch:

sudo pacman -S restic

Manual download (any distro):

# Download latest release (check https://github.com/restic/restic/releases for current version)
curl -LO https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_linux_amd64.bz2

# Extract and install
bunzip2 restic_*.bz2
chmod +x restic_*
sudo mv restic_* /usr/local/bin/restic

4.2 Create Configuration

mkdir -p ~/.config/restic

Create ~/.config/restic/env.sh:

export RESTIC_REPOSITORY="s3:s3.us-west-004.backblazeb2.com/YOUR-BUCKET-NAME"
export RESTIC_PASSWORD="YOUR_SECURE_PASSWORD_HERE"
export AWS_ACCESS_KEY_ID="YOUR_B2_KEY_ID"
export AWS_SECRET_ACCESS_KEY="YOUR_B2_APPLICATION_KEY"

Secure the file:

chmod 600 ~/.config/restic/env.sh

Replace the placeholder values with your actual credentials.

4.3 Create Exclude File

Create ~/.config/restic/excludes.txt:

# System
.cache
.local/share/Trash
.thumbnails
*.tmp
*.temp
*~

# Browser caches (bookmarks/passwords ARE backed up)
.config/google-chrome/*/Cache
.config/google-chrome/*/Code Cache
.config/google-chrome/*/GPUCache
.config/BraveSoftware/Brave-Browser/*/Cache
.config/BraveSoftware/Brave-Browser/*/Code Cache
.mozilla/firefox/*/cache2
.mozilla/firefox/*/storage

# App caches
.config/Slack/Cache
.config/discord/Cache
.config/spotify/Data
.var/app/*/cache

# Development
node_modules
.venv
__pycache__
*.pyc
.npm
.cargo/registry
target

# Flatpak app data (often large, app-specific)
.var/app/*/cache

# Large/replaceable
Downloads/*.iso
Downloads/*.AppImage
*.log

4.4 Initialise Repository

source ~/.config/restic/env.sh
restic init

Save the repository ID shown.

4.5 First Backup

Recommended: Exclude large files

Add --exclude-larger-than 300M to skip files over 300MB. Large files (videos, ISOs, game assets) are often replaceable and expensive to store in the cloud.

restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --verbose

Optional: Test with a dry run first

source ~/.config/restic/env.sh
restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --dry-run -vv

This shows what would be backed up without uploading anything.

Run the actual backup:

source ~/.config/restic/env.sh
restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --verbose

4.6 Automate with systemd

Create the backup script ~/.config/restic/backup.sh:

#!/bin/bash
source ~/.config/restic/env.sh

# Run backup (skip files >300MB)
restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M --quiet

# Clean up old snapshots (keep 7 daily, 4 weekly, 6 monthly)
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

Make it executable:

chmod +x ~/.config/restic/backup.sh

Create the systemd service ~/.config/systemd/user/restic-backup.service:

[Unit]
Description=Restic backup to Backblaze B2

[Service]
Type=oneshot
ExecStart=%h/.config/restic/backup.sh

Create the timer ~/.config/systemd/user/restic-backup.timer:

[Unit]
Description=Daily restic backup

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1800

[Install]
WantedBy=timers.target

Enable and start the timer:

mkdir -p ~/.config/systemd/user
# (copy the service and timer files above)
systemctl --user daemon-reload
systemctl --user enable --now restic-backup.timer

Notes:

  • Persistent=true runs a missed backup when the computer next wakes (laptop-friendly)
  • RandomizedDelaySec=1800 staggers the backup within a 30-minute window (avoids thundering herd if you have multiple machines)
  • %h expands to your home directory in systemd unit files

4.7 Verify It’s Working

# Check timer status
systemctl --user list-timers restic-backup.timer

# Check last run
journalctl --user -u restic-backup.service -n 20

# View snapshots
source ~/.config/restic/env.sh
restic snapshots

Restoring Files

List Your Backups

# macOS/Linux
source ~/.config/restic/env.sh
restic snapshots

# Windows PowerShell
. "$env:USERPROFILE\.config\restic\env.ps1"
restic snapshots

Restore a Specific File

# Find which snapshots contain your file
restic find "important-document.docx"

# Restore from latest backup
restic restore latest --target ~/restore-test --include "Documents/important-document.docx"

Restore Everything (Disaster Recovery)

On a fresh machine:

  1. Install restic
  2. Create env file with your credentials
  3. Run:
source ~/.config/restic/env.sh
restic restore latest --target /

Costs

Data SizeMonthly Cost
50 GB~$0.30
100 GB~$0.60
500 GB~$3.00
1 TB~$6.00
  • Upload: Free
  • Download: First 3ร— your storage is free, then $0.01/GB
  • API calls: Essentially free for personal use

Maintenance

Check Backup Health

Quick check (run monthly):

source ~/.config/restic/env.sh  # or PowerShell equivalent
restic check

This validates the repository structure (snapshots, metadata, pack files).

Thorough check (run quarterly):

restic check --read-data

This downloads and verifies all backed-up data. Takes longer and uses bandwidth, but confirms your data is actually intact.

Partial check (compromise):

restic check --read-data-subset=10%

Randomly verifies 10% of your data โ€” useful for large repositories.

View Backup Statistics

restic stats
restic stats latest

Manual Backup

Run anytime you want an immediate backup:

source ~/.config/restic/env.sh
restic backup ~ --exclude-file="$HOME/.config/restic/excludes.txt" --exclude-larger-than 300M

Manual Prune (If Not Automated)

If you’re not running automatic pruning, periodically clean up old snapshots:

# Preview what would be removed (recommended first)
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --dry-run

# Actually remove old snapshots and reclaim space
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

# Verify repository integrity after pruning
restic check

Optional: Automatic Git Commits for Your Notes

If you use Obsidian, Logseq, or any markdown-based notes system, git provides fine-grained version history that restic’s daily snapshots can’t match. Accidentally delete a paragraph? Git has it from 10 minutes ago.

Why Git + Restic?

They solve different problems:

  • Git: Tracks every change to text files, lets you see exactly what changed and when
  • Restic: Backs up everything (including large files git ignores) to the cloud

Use both.

Windows: Git Setup

Install git:

winget install --id Git.Git -e --source winget

Initialise your vault:

cd "$env:USERPROFILE\Documents\Obsidian Vault"  # Your vault path
git init
git config user.email "autobackup@local"
git config user.name "Auto-Backup"

Create .gitignore (track only markdown):

# Ignore everything
*

# But track markdown
!*.md

# Track Obsidian config
!.obsidian/
!.obsidian/**

# Track folder structure
!*/

# Track this file
!.gitignore

# Ignore workspace (local state)
.obsidian/workspace*.json

# Ignore trash
.trash/

Create hourly commit script %USERPROFILE%\.config\restic\git-commit.ps1:

$vaultPath = "$env:USERPROFILE\Documents\Obsidian Vault"  # Your vault path

Set-Location $vaultPath

# Check if there are changes
$status = git status --porcelain
if (-not $status) {
    Write-Host "No changes to commit"
    exit 0
}

# Commit all changes
git add -A
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm"
git commit -m "Auto-backup: $timestamp"

Write-Host "Committed changes at $timestamp"

Schedule every 10 minutes: Create a Task Scheduler task using the same process as the backup task but with:

  • Program: powershell.exe
  • Arguments: -ExecutionPolicy Bypass -File "%USERPROFILE%\.config\restic\git-commit.ps1"
  • Trigger: Daily, repeat every 10 minutes

macOS: Git Setup

Install git (usually pre-installed, or):

xcode-select --install

Initialise your vault:

cd ~/Documents/Obsidian\ Vault  # Your vault path
git init
git config user.email "autobackup@local"
git config user.name "Auto-Backup"

Create .gitignore (same content as Windows above).

Create hourly commit script ~/.config/restic/git-commit.sh:

#!/bin/bash
VAULT_PATH="$HOME/Documents/Obsidian Vault"  # Your vault path

cd "$VAULT_PATH" || exit 1

# Check if there are changes
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
    echo "No changes to commit"
    exit 0
fi

# Commit all changes
git add -A
TIMESTAMP=$(date "+%Y-%m-%d %H:%M")
git commit -m "Auto-backup: $TIMESTAMP"

echo "Committed changes at $TIMESTAMP"

Make executable:

chmod +x ~/.config/restic/git-commit.sh

Schedule every 10 minutes: Create ~/Library/LaunchAgents/com.git.autocommit.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.git.autocommit</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>/Users/YOURUSERNAME/.config/restic/git-commit.sh</string>
    </array>
    <key>StartInterval</key>
    <integer>600</integer>
    <key>StandardOutPath</key>
    <string>/tmp/git-commit.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/git-commit.log</string>
</dict>
</plist>

Replace YOURUSERNAME, then load:

launchctl load ~/Library/LaunchAgents/com.git.autocommit.plist

Linux: Git Setup

Install git (usually pre-installed, or):

# Debian/Ubuntu
sudo apt install git

# Fedora
sudo dnf install git

Initialise your vault:

cd ~/Documents/Obsidian\ Vault  # Your vault path
git init
git config user.email "autobackup@local"
git config user.name "Auto-Backup"

Create .gitignore (same content as Windows/macOS above).

Create commit script ~/.config/restic/git-commit.sh:

#!/bin/bash
VAULT_PATH="$HOME/Documents/Obsidian Vault"  # Your vault path

cd "$VAULT_PATH" || exit 1

# Check if there are changes
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
    exit 0
fi

# Commit all changes
git add -A
TIMESTAMP=$(date "+%Y-%m-%d %H:%M")
git commit -m "Auto-backup: $TIMESTAMP"

Make executable:

chmod +x ~/.config/restic/git-commit.sh

Schedule every 10 minutes with systemd:

Create ~/.config/systemd/user/git-autocommit.service:

[Unit]
Description=Auto-commit git changes

[Service]
Type=oneshot
ExecStart=%h/.config/restic/git-commit.sh

Create ~/.config/systemd/user/git-autocommit.timer:

[Unit]
Description=Auto-commit git changes every 10 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=10min

[Install]
WantedBy=timers.target

Enable:

systemctl --user daemon-reload
systemctl --user enable --now git-autocommit.timer

Recovering from Git

# See recent commits
git log --oneline -20

# See what changed in a commit
git show abc1234

# Restore a deleted file
git checkout HEAD~1 -- "path/to/file.md"

# See file at a specific time
git show HEAD~5:"path/to/file.md"

Optional: Weekly Filesystem Manifests

A manifest is a snapshot of all your files โ€” including those excluded from backup (like large videos). It records file paths, sizes, and SHA256 hashes. This serves two purposes:

  1. Know what you had: If a large file is lost, you at least know it existed and what it was
  2. Detect tampering: If ransomware encrypts files, hash mismatches reveal the damage โ€” even before you notice

Why SHA256, not MD5? MD5 is cryptographically broken โ€” collisions can be deliberately crafted. Sophisticated ransomware could theoretically replace a file with a collision that has the same MD5 hash. SHA256 prevents this. The speed difference is negligible for personal use.

Windows Manifest Script

Create %USERPROFILE%\.config\restic\manifest.ps1:

# Weekly filesystem manifest generator
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$outputDir = "$env:USERPROFILE\.config\restic\manifests"
$manifestFile = "$outputDir\manifest_$timestamp.tsv"

# Create output directory
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null

# Generate manifest (this may take 10-30 minutes)
Write-Host "Generating filesystem manifest..."
Get-ChildItem -Path $env:USERPROFILE -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
    try {
        $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash
        "$($_.Length)`t$($_.LastWriteTime.ToString('yyyy-MM-dd_HH:mm:ss'))`t$hash`t$($_.FullName)"
    } catch {
        # Skip files we can't read
    }
} | Out-File -FilePath $manifestFile -Encoding UTF8

# Compress to save space
Compress-Archive -Path $manifestFile -DestinationPath "$manifestFile.zip" -Force
Remove-Item $manifestFile

# Keep only last 8 manifests
Get-ChildItem "$outputDir\manifest_*.zip" | Sort-Object LastWriteTime -Descending | Select-Object -Skip 8 | Remove-Item -Force

Write-Host "Manifest saved: $manifestFile.zip"

Schedule it weekly: Use Task Scheduler (same process as the backup task), set to run weekly on Sunday at 3 AM.

macOS Manifest Script

Create ~/.config/restic/manifest.sh:

#!/bin/bash
# Weekly filesystem manifest generator

TIMESTAMP=$(date "+%Y-%m-%d_%H-%M-%S")
OUTPUT_DIR="$HOME/.config/restic/manifests"
MANIFEST_FILE="$OUTPUT_DIR/manifest_$TIMESTAMP.tsv"

mkdir -p "$OUTPUT_DIR"

echo "Generating filesystem manifest..."

# Header
echo -e "SIZE\tMODIFIED\tSHA256\tPATH" > "$MANIFEST_FILE"

# Generate manifest (this may take 10-30 minutes)
find "$HOME" -type f -print0 2>/dev/null | while IFS= read -r -d '' file; do
    size=$(stat -f%z "$file" 2>/dev/null || echo "0")
    mtime=$(stat -f "%Sm" -t "%Y-%m-%d_%H:%M:%S" "$file" 2>/dev/null || echo "unknown")
    hash=$(shasum -a 256 "$file" 2>/dev/null | cut -d' ' -f1 || echo "error")
    printf "%s\t%s\t%s\t%s\n" "$size" "$mtime" "$hash" "$file"
done >> "$MANIFEST_FILE"

# Compress
gzip -f "$MANIFEST_FILE"

# Keep only last 8 manifests
ls -t "$OUTPUT_DIR"/manifest_*.tsv.gz 2>/dev/null | tail -n +9 | xargs -r rm -f

echo "Manifest saved: $MANIFEST_FILE.gz"

Make it executable:

chmod +x ~/.config/restic/manifest.sh

Schedule it weekly: Create ~/Library/LaunchAgents/com.restic.manifest.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.restic.manifest</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>/Users/YOURUSERNAME/.config/restic/manifest.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Weekday</key>
        <integer>0</integer>
        <key>Hour</key>
        <integer>3</integer>
    </dict>
    <key>StandardOutPath</key>
    <string>/tmp/restic-manifest.log</string>
    <key>StandardErrorPath</key>
    <string>/tmp/restic-manifest.log</string>
</dict>
</plist>

Replace YOURUSERNAME with your actual username, then load it:

launchctl load ~/Library/LaunchAgents/com.restic.manifest.plist

Linux Manifest Script

Create ~/.config/restic/manifest.sh:

#!/bin/bash
# Weekly filesystem manifest generator

TIMESTAMP=$(date "+%Y-%m-%d_%H-%M-%S")
OUTPUT_DIR="$HOME/.config/restic/manifests"
MANIFEST_FILE="$OUTPUT_DIR/manifest_$TIMESTAMP.tsv"

mkdir -p "$OUTPUT_DIR"

echo "Generating filesystem manifest..."

# Header
echo -e "SIZE\tMODIFIED\tSHA256\tPATH" > "$MANIFEST_FILE"

# Generate manifest (this may take 10-30 minutes)
find "$HOME" -type f -print0 2>/dev/null | while IFS= read -r -d '' file; do
    size=$(stat -c%s "$file" 2>/dev/null || echo "0")
    mtime=$(date -d @"$(stat -c %Y "$file" 2>/dev/null)" +"%Y-%m-%d_%H:%M:%S" 2>/dev/null || echo "unknown")
    hash=$(sha256sum "$file" 2>/dev/null | cut -d' ' -f1 || echo "error")
    printf "%s\t%s\t%s\t%s\n" "$size" "$mtime" "$hash" "$file"
done >> "$MANIFEST_FILE"

# Compress
gzip -f "$MANIFEST_FILE"

# Keep only last 8 manifests
ls -t "$OUTPUT_DIR"/manifest_*.tsv.gz 2>/dev/null | tail -n +9 | xargs -r rm -f

echo "Manifest saved: $MANIFEST_FILE.gz"

Make it executable:

chmod +x ~/.config/restic/manifest.sh

Schedule weekly with systemd:

Create ~/.config/systemd/user/restic-manifest.service:

[Unit]
Description=Generate filesystem manifest

[Service]
Type=oneshot
ExecStart=%h/.config/restic/manifest.sh

Create ~/.config/systemd/user/restic-manifest.timer:

[Unit]
Description=Weekly filesystem manifest

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

[Install]
WantedBy=timers.target

Enable:

systemctl --user daemon-reload
systemctl --user enable --now restic-manifest.timer

What the Manifest Captures

Each line contains:

  • Size (bytes)
  • Modified time
  • SHA256 hash
  • Full path

The compressed manifest is typically 5-50MB depending on file count. Keep it with your backups โ€” restic will back up the manifest directory automatically.

Comparing Manifests

To detect changes between two manifests:

# Extract and compare (macOS/Linux)
zdiff manifest_old.tsv.gz manifest_new.tsv.gz | head -50

Or use a diff tool on the extracted TSV files. Look for:

  • Hash changes without expected modification time changes (possible tampering)
  • Mass hash changes on the same date (possible ransomware)
  • Missing files you didn’t delete

Troubleshooting

Understanding Exit Codes

Restic uses exit codes to indicate what happened:

  • 0: Success โ€” backup completed normally
  • 1: Fatal error โ€” no snapshot created
  • 3: Partial success โ€” some files couldn’t be read, but snapshot was created

Exit code 3 is common if some files are locked by other applications. The backup still succeeds for everything else.

“repository does not exist”

Check your RESTIC_REPOSITORY path. The format is:

s3:s3.REGION.backblazeb2.com/BUCKET-NAME

“invalid credentials”

Verify AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY match your B2 application key exactly.

Backup is slow

First backup uploads everything โ€” this is normal. Subsequent backups only upload changes and should be much faster.

To limit bandwidth during work hours, add --limit-upload 5000 (5 MB/s) to your backup command.

Task Scheduler / launchd not running

Windows: Check Task Scheduler โ†’ Task History. Common issues:

  • PowerShell execution policy blocking the script
  • Wrong path to script

macOS: Check /tmp/restic-backup.log for errors. Common issues:

  • Needs Full Disk Access
  • $HOME not expanded in plist (use full path)

Key Points

  • Save your password. If you lose it, your backups are unrecoverable. Put it in your password manager.
  • Test restores. Periodically restore a file to verify your backups work.
  • First backup takes time. Let it run overnight if needed.
  • This complements sync services. Use OneDrive/iCloud for convenience, restic for disaster recovery.