🏰 Restic backups on autopilot! - Automated encrypted backups to Hetzner Storage Box. One-script setup, deduplication, snapshots.
Find a file
2025-11-24 15:41:58 +01:00
docs Add docs/CHEATSHEET.md 2025-10-28 22:58:53 +01:00
systemd Update systemd/restic-fortress.timer 2025-10-28 17:56:23 +01:00
.gitignore Add .gitignore 2025-10-28 11:58:41 +01:00
.restic-exclude Add .restic-exclude 2025-10-28 12:04:03 +01:00
LICENSE Update LICENSE 2025-10-28 22:41:24 +01:00
README.md Update README.md 2025-11-24 15:41:58 +01:00
restic-fortress.sh Update restic-fortress.sh 2025-11-24 15:13:24 +01:00
restore-example.sh Add restore-example.sh 2025-10-28 12:04:24 +01:00
setup-restic-fortress.sh Update setup-restic-fortress.sh 2025-10-28 22:00:26 +01:00

Restic Backup System for Hetzner Storage Box (sftp server)


🎯 Intro: Why Restic (and why it's better than plain rsync)

Restic is a modern backup program that goes far beyond what rsync can do:

  • Encrypted - Data encrypted before upload (AES-256, client-side)
  • Deduplicated - Only stores unique data blocks (saves massive space)
  • Incremental - Only uploads changed data (faster than full copies)
  • Verified - Built-in integrity checking (knows if data is corrupted)
  • Snapshots - Keep multiple versions without duplicating everything
  • Compression - Automatic compression (saves bandwidth and space)

Simple comparison:

Feature rsync Restic
Encrypted at rest No Yes (AES-256)
Deduplication No Yes (block-level)
Multiple versions Manual Automatic snapshots
Integrity checking ⚠️ Basic Cryptographic verification
Space efficiency 1:1 copy Only unique blocks stored
Versioning Manual rotation Built-in snapshot management
Restore flexibility ⚠️ All or nothing Mount and browse any version

Example: With rsync, 10 daily backups = 10× the data. With Restic, 10 daily backups might only be 2× the data (thanks to deduplication).


📁 Repository Structure

restic-fortress/
├── README.md                    # This file
├── setup-restic-fortress.sh       # One-time setup script
├── restic-fortress.sh             # Main backup script
├── restore-example.sh           # Restore examples
├── .restic-exclude              # Exclusion patterns
├── systemd/
│   ├── restic-fortress.service    # Systemd service
│   └── restic-fortress.timer      # Systemd timer
└── docs/
    ├── CHEATSHEET.md            # basic and fast restic-commands lookup cheetsheet
    ├── ADVANCED.md              # very advanced/detailed restic usage/commands
    └── TROUBLESHOOTING.md       # Detailed troubleshooting

Complete setup and backup solution using Restic for encrypted, deduplicated backups to Hetzner Storage Box.

📋 README.md Table of Contents


🎯 Overview

This repository contains scripts and documentation for setting up automated, encrypted backups using Restic to a Hetzner Storage Box. All backups are encrypted client-side before upload, ensuring your data remains private.

What can be backed up:

  • System snapshots
  • Configuration files
  • User data
  • Any specified directories

Where it goes (Backup-Destination):

  • Hetzner Storage Box (via encrypted SFTP traffic)
  • Alternatively: Use any other sftp Storage (sftp-support in restic is native)
  • Client-side encryption (AES-256)
  • Automatic deduplication

Features

  • Fully Automated Setup - One script does everything
  • Client-Side Encryption - Data encrypted before upload (AES-256)
  • Deduplication - Only unique data blocks are stored
  • Incremental Backups - Only changed data is transferred
  • Snapshot Management - Keep daily/weekly/monthly backups
  • Easy Restore - Browse backups like a filesystem
  • SSH Key Authentication - Passwordless, secure access
  • Automated Cleanup - Old backups pruned automatically
  • Idempotent - Scripts safe to run multiple times

📦 Requirements

System Requirements

  • OS: Arch Linux, Debian, Ubuntu, Fedora, or derivatives
  • Root access: Required for system backups
  • Network: Stable internet connection
  • FUSE: Required for mounting backups (auto-installed by setup script)

External Requirements

  • Hetzner Storage Box account (Get one here)
    • Any size plan works (starts at €3.20/month for 1TB)
    • SFTP/SSH access enabled

Software Dependencies

(Automatically installed by setup script)

  • restic - Backup program
  • openssh - SSH client
  • openssl - Password generation
  • fuse3 - For mounting backups

🚀 Quick Start

# 1. Clone repository
git clone https://git.wiotte.de/matteo/restic-fortress.git
cd restic-fortress

# 2. Edit configuration
nano setup-restic-fortress.sh
# Update at least (!): see for more options the section "Configuration"
$SSH_USER
$SSH_HOST
$SSH_PORT
$SSH_KEY_NAME

# 3. Run setup (will install dependencies, create keys, initialize repo)
sudo bash ./setup-restic-fortress.sh

# 4. Save the encryption password shown!

# 5. Run first backup
sudo bash ./restic-fortress.sh

# hint:
# if running "restic" command manually, you have to add environment variables before. like:
export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"
sudo -E restic snapshots

⚙️ Configuration + Default Usage

Step 1: Configure Setup Script

Edit setup-restic-fortress.sh and update the following values:

nano setup-restic-fortress.sh
# ============================================================================
# CONFIGURATION - EDIT THESE VALUES
# ============================================================================
#
# ⚠️  Review settings below before running backups

# SSH_USER: ssh/sftp user for storagebox/sftp server
#   for hetzner storagebox its normaly something like: "u369503-sub3"
#   auto created by hetzner in the hetzner console (where you manage your storagebox)
SSH_USER="u369503-sub3"

# SSH_HOST: Server Host or IP (Backup-Destination)
SSH_HOST="u369503-sub3.your-storagebox.de"

# SSH_PORT: ssh/sftp port
#   for hetzner storagebox its always port 23
SSH_PORT="23"

# SSH_KEY_DIR: where to store the newly created ssh keys (must be a directory)
#   ⚠️ best if not changed!
SSH_KEY_DIR="/root/.ssh/storagebox-hetzner"

# SSH_KEY_NAME: name for the newly created ssh key (stored in $SSH_KEY_DIR)
#   there will be 2 files, a key-pair, after creation: the private key and public key (.pub file)
#   private key remains on the machine from where you want to access the Storagebox/SFTP Server
#   public key will be transfered to the Storagebox/SFTP Server by the script
SSH_KEY_NAME="u369503-sub3"

# SSH_CONFIG: path to your ssh config
#   ⚠️ best if not changed!
SSH_CONFIG="/root/.ssh/config"

# RESTIC_REPO_NAME: Repository folder name on Hetzner
#   set to the desired name of the restic repository we are creating now
RESTIC_REPO_NAME="restic-backups"

# RESTIC_PASSWORD_FILE: where to store the password file for the encryption of the restic repository
#   ⚠️ best if not changed!
RESTIC_PASSWORD_FILE="$HOME/.restic-password"

Step 2: Run Setup Script

chmod +x setup-restic-fortress.sh # make the script executable
sudo bash ./setup-restic-fortress.sh

The script will:

  1. Detect your Linux distribution
  2. Install required packages (restic, openssh, openssl, fuse3)
  3. Generate Ed25519 SSH key pair
  4. Upload public key to Hetzner (you'll enter password once)
  5. Create SSH config for easy access
  6. Generate strong encryption password
  7. Initialize encrypted repository on Hetzner Storagebox/SFTP Server
  8. Verify everything works

Step 3: Save Your Encryption Password

⚠️ CRITICAL: The script will show your encryption password. Save it in multiple secure locations (examples):

  • Password manager (Bitwarden, Keepassium, etc.)
  • Printed paper in a safe/vault
  • Secure encrypted note on another device

Without this password, your backups are unrecoverable!

Step 4: Backup Script Configuration

Edit restic-fortress.sh:

nano setup-restic-fortress.sh
# ============================================================================
# CONFIGURATION - EDIT THESE VALUES 
# ============================================================================
# 
# ⚠️  Review and update these settings before running backups

# BACKUP_SOURCE: Directory to backup
#   Examples: /home/user, /var/www, /etc, ~/Documents
BACKUP_SOURCE="$DIR_TO_BACKUP"

# RESTIC_REPO_NAME: Repository folder name on Hetzner
#   Change this for different backup sets (e.g., "laptop-backups", "server-prod")
#   was set when running setup-restic-fortress.sh
RESTIC_REPO_NAME="restic-backups"

# RESTIC_PASSWORD_FILE: Encryption password location
#   ⚠️  CRITICAL: Without this password, backups are UNRECOVERABLE!
#   Created by setup-restic-fortress.sh - keep it safe and backed up!
RESTIC_PASSWORD_FILE="/home/matteo/.restic-password"

# HOSTNAME: Computer identifier for backup tags
#   Tip: Run 'hostname' to see your system's name
HOSTNAME="septimus"

# SSH_CONFIG_ALIAS: SSH config alias (from setup-restic-fortress.sh)
#   This matches the "Host" entry created in /root/.ssh/config
SSH_CONFIG_ALIAS="hetzner-sub3"

# Retention policy (how long to keep backups)
DAILY="7"         # Keep 7 daily backups
WEEKLY="4"        # Keep 4 weekly backups
MONTHLY="6"       # Keep 6 monthly backups

Step 5: Create your first Backup

sudo bash ./restic-fortress.sh

💻 Advanced Usage

Manual Usage of Restic command(s) (without restic-fortress.sh script)

⚠️ Important: All manual restic commands require environment variables. The backup script handles env vars automatically. The environment variables need to be edited according your values before being used.

Option A: Export variables manually:

export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"

# Use sudo -E to preserve environment
sudo -E restic snapshots

Option B: Specify inline:

sudo RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups" \
     RESTIC_PASSWORD_FILE="/home/user/.restic-password" \
     restic snapshots

Multiple Backup Sources

To backup multiple directories:

restic backup \
    /home \
    /etc \
    /var/log \
    --exclude-caches \
    --exclude '/home/*/.cache'

Exclusion Patterns

Create .restic-exclude file:

# Exclude patterns
*.tmp
*.cache
node_modules/
.git/
__pycache__/

Use in backup:

restic backup /home --exclude-file=.restic-exclude

List Backups

# Using script's env
export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"

sudo -E restic snapshots
sudo -E restic snapshots --latest 10

Browse Backups

# Install FUSE if not already installed
sudo pacman -S fuse3  # Arch
sudo apt install fuse3  # Debian/Ubuntu

# Set environment
export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"

# Mount repository
sudo mkdir -p /mnt/restic
sudo -E restic mount /mnt/restic

# Browse in another terminal
cd /mnt/restic/snapshots/latest/
ls -la

# Unmount when done (Ctrl+C in mount terminal)

Check Repository Health

export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"
sudo -E restic check

View Repository Stats

export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"

# Default: restore-size (size after decompression)
sudo -E restic stats

# Actual size on storage backend
sudo -E restic stats --mode raw-data

# Size deduplicated by contents
sudo -E restic stats --mode files-by-contents

🤖 Automation/Schedule

Option 1: Cron (Simple)

Edit root's crontab:

sudo crontab -e

Add daily backup at 3 AM:

0 3 * * * /root/restic-fortress.sh >> /var/log/restic-fortress.log 2>&1

Create service file:

sudo nano /etc/systemd/system/restic-fortress.service

Adjust the execution path in service file:

ExecStart=/root/restic-fortress.sh
[Unit]
Description=Restic Backup
After=network-online.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/bash /root/restic-fortress/restic-fortress.sh
StandardOutput=journal
StandardError=journal
# Prevent multiple instances from running simultaneously
# If service is already running, new starts will be ignored
RemainAfterExit=no

Create timer file:

sudo nano /etc/systemd/system/restic-fortress.timer

Adjust the execution time/scheduling in timer file:

OnCalendar=*-*-* 03:00:00

See here for a very well done timer-formatting explanation

[Unit]
Description=Run Restic Backup Daily

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

[Install]
WantedBy=timers.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-fortress.timer
sudo systemctl status restic-fortress.timer

View logs:

sudo journalctl -u restic-fortress.service -f

Test/run manually:

sudo systemctl start restic-fortress.service --no-block

🔄 Restore Guide

⚠️ All restore commands require environment variables: Adjust them before execution!

export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"

Restore Latest Snapshot

# Restore everything
sudo -E restic restore latest --target /tmp/restore

# Restore specific directory using path
sudo -E restic restore latest --target /tmp/restore \
    --path /home/user/documents

Restore Specific Snapshot

# List snapshots to get snapshot ID
sudo -E restic snapshots
# Example output:
# ID        Time                 Host        Tags
# a1b2c3d4  2025-10-28 03:00:00  myhost      daily

# Restore that specific snapshot (use 8-char hex ID)
sudo -E restic restore a1b2c3d4 --target /tmp/restore

Restore Single File

# Method 1: Find the file first
sudo -E restic find myfile.txt
# Shows: Found matching entries: /home/user/documents/myfile.txt

# Restore using pattern match
sudo -E restic restore latest --target /tmp/restore \
    --include '**/myfile.txt'

# Method 2: Restore entire directory containing the file
sudo -E restic restore latest --target /tmp/restore \
    --path /home/user/documents

Mount and Browse

# Ensure FUSE is installed
sudo pacman -S fuse3  # Arch
sudo apt install fuse3  # Debian/Ubuntu

# Mount all snapshots
sudo mkdir -p /mnt/restic
sudo -E restic mount /mnt/restic

# Browse in file manager or another terminal
cd /mnt/restic/snapshots/
ls -la  # Shows all snapshots

cd latest/home/user/documents/
cp myfile.txt ~/restored/

# Or use GUI file manager
nautilus /mnt/restic/snapshots/latest/  # GNOME
dolphin /mnt/restic/snapshots/latest/   # KDE

# Unmount when done (Ctrl+C in mount terminal)

🔧 Basic Troubleshooting

SSH Connection Issues

Problem: "Permission denied" or "Connection refused"

Solution:

# Test SSH connection
sudo ssh hetzner-sub3 "echo 'Connection OK'"

# Check SSH key exists
ls -la /root/.ssh/storagebox-hetzner/

# Check SSH config
cat /root/.ssh/config | grep -A5 "Host hetzner-sub3"

# Re-upload key if needed
cat /root/.ssh/storagebox-hetzner/u123456-sub1.pub | \
    ssh -p23 u123456-sub1@u123456-sub1.your-storagebox.de install-ssh-key

Repository Not Found

Problem: "repository does not exist"

Solution:

# Check repository path on Hetzner
sudo ssh hetzner-sub3 "ls -la"

# Verify environment variables
echo $RESTIC_REPOSITORY
echo $RESTIC_PASSWORD_FILE

# Check if you used different path during init
sudo ssh hetzner-sub3 "find . -name config -type f"

# Re-initialize if needed (WARNING: creates new repo)
export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"
sudo -E restic init

Password Issues

Problem: "wrong password" or "unable to open config"

Solution:

# Verify password file exists and has content
cat $RESTIC_PASSWORD_FILE

# Check file permissions (should be 600)
ls -la $RESTIC_PASSWORD_FILE

# Fix permissions if needed
sudo chmod 600 $RESTIC_PASSWORD_FILE

# Verify you're using the correct password file path
echo $RESTIC_PASSWORD_FILE

# Test with explicit password (temporarily)
sudo RESTIC_PASSWORD="your-actual-password" \
     RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups" \
     restic snapshots

Environment Variables Not Working

Problem: restic says "Please specify repository location"

Solution:

# Verify variables are exported
export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"

# IMPORTANT: Use sudo -E to preserve environment
sudo -E restic snapshots

# OR specify inline
sudo RESTIC_REPOSITORY="..." RESTIC_PASSWORD_FILE="..." restic snapshots

# OR add to script
echo 'export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"' | sudo tee -a /root/.bashrc
echo 'export RESTIC_PASSWORD_FILE="/home/user/.restic-password"' | sudo tee -a /root/.bashrc

Backup Fails with "no space left"

Problem: Hetzner storage box full

Solution:

export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
export RESTIC_PASSWORD_FILE="/home/user/.restic-password"

# Check repository size
sudo -E restic stats --mode raw-data

# Remove old snapshots
sudo -E restic forget --keep-daily 3 --keep-weekly 2 --prune

# Check again
sudo -E restic stats --mode raw-data

# Check Hetzner quota
sudo ssh hetzner-sub3 "quota"

Slow Backups

Problem: Backups take very long

Solutions:

  1. Exclude unnecessary files:
   restic backup /home \
       --exclude '*.cache' \
       --exclude '*.tmp' \
       --exclude 'node_modules/' \
       --exclude '.git/'
  1. Optimize compression (use larger pack size):
   restic backup /home --pack-size 128
  1. Limit bandwidth if ISP throttles:
   restic backup /home --limit-upload 10000  # 10 MB/s
  1. Skip directory scanning (if unchanged):
   restic backup /home --no-scan
  1. Check network speed:
   # Test upload speed to Hetzner
   dd if=/dev/zero bs=1M count=100 | \
       ssh hetzner-sub3 "cat > /dev/null"

FUSE Mount Fails

Problem: "fusermount: failed to open /dev/fuse"

Solution:

# Install FUSE
sudo pacman -S fuse3  # Arch
sudo apt install fuse3  # Debian/Ubuntu

# Load FUSE kernel module
sudo modprobe fuse

# Check if loaded
lsmod | grep fuse

# Make it load at boot
echo "fuse" | sudo tee /etc/modules-load.d/fuse.conf

# Check user permissions
ls -la /dev/fuse
# Should show: crw-rw-rw- 1 root root

# Add user to fuse group (if needed)
sudo usermod -aG fuse $USER

🔐 Security Notes

What's Encrypted

All file contents (AES-256)
All file metadata (names, sizes, dates)
Directory structure
Everything - Hetzner cannot read your data

What's NOT Encrypted

⚠️ Connection metadata (SSH logs show you connected)
⚠️ Approximate repository size (Hetzner can see disk usage)
⚠️ Timing of backups (connection timestamps)
⚠️ Number of snapshots (visible as file count)

Security Best Practices

  1. Protect Your Password

    • Store in password manager
    • Keep offline backup (printed, in safe)
    • Never share or commit to git
    • Use strong, random password (32+ chars)
  2. Secure SSH Keys

    • Keys stored in /root/.ssh/ (root only)
    • Use Ed25519 (modern, secure)
    • Never copy private key elsewhere
    • Permissions: 600 (private), 644 (public)
  3. Regular Security Checks

   # Verify repository integrity
   export RESTIC_REPOSITORY="sftp:hetzner-sub3:restic-backups"
   export RESTIC_PASSWORD_FILE="/home/user/.restic-password"
   sudo -E restic check
   
   # Check for unauthorized access
   sudo ssh hetzner-sub3 "tail -20 .hsh_history"
  1. Test Restores Regularly
    • Test restoring files quarterly
    • Verify data integrity after restore
    • Practice disaster recovery procedures
    • Document restore process

Threat Model

Threat Protected? Notes
Hetzner breach Yes Data is encrypted with your password
Hetzner employee snooping Yes Cannot decrypt without password
Stolen laptop/server Yes Keys are root-only, encrypted backups
Lost/stolen password file No Need physical access to machine
SSH key compromise ⚠️ Partial Can access repo but not decrypt data
Lost encryption password Fatal Backups permanently unrecoverable
Man-in-the-middle attack Yes SSH encryption + host key verification
Ransomware Yes Offline backups, read-only snapshots

Compliance Considerations

GDPR Compliance:

  • Client-side encryption (data minimization)
  • Right to erasure (restic forget + prune)
  • Data portability (restic restore)
  • ⚠️ Hetzner is data processor (DPA needed)

For sensitive data:

  • Consider geographic restrictions (Hetzner has multiple locations)
  • Review Hetzner's data processing agreement
  • Implement additional access controls
  • Maintain audit logs

🤝 Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Test your changes thoroughly
  4. Commit your changes (git commit -m 'Add amazing feature')
  5. Push to the branch (git push origin feature/amazing-feature)
  6. Open a Pull Request

What COULD be added:

  • Email notifications on backup failure
  • Backup multiple hosts to same repository
  • GUI for restore operations
  • Integration with monitoring systems (Prometheus, Grafana)
  • Backup verification automation
  • S3 backend support
  • Backup rotation visualization
  • Pre/post backup hooks
  • Backup performance metrics

📄 License

MIT License - See LICENSE file


⚠️ Disclaimer

This software is provided "as is" without warranty of any kind, express or implied. Always test your backups and verify restores work before relying on them in production. The authors and contributors are not responsible for data loss.

CRITICAL REMINDER:

  • Test your backups regularly
  • Verify restores actually work
  • Keep multiple copies of your encryption password
  • Without the password, backups are UNRECOVERABLE

🆘 Support & Security Issues

Issues?

Security Issues?

  • DO NOT open public issues for security vulnerabilities
  • Email: security@wiotte.de
  • Use GPG for sensitive communications

📊 Status

Backup Status Tests License Restic


Used Restic Version: 0.17+
Tested On: Arch Linux (Garuda), Debian 12 Maintainer: Matteo