| docs | ||
| systemd | ||
| .gitignore | ||
| .restic-exclude | ||
| LICENSE | ||
| README.md | ||
| restic-fortress.sh | ||
| restore-example.sh | ||
| setup-restic-fortress.sh | ||
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
- Features
- Requirements
- Quick Start
- Configuration + Default Usage
- Advanced Usage
- Automation/Schedule
- Restore Guide
- Basic Troubleshooting
- Security Notes
- Contributing
- Disclaimer
🎯 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 programopenssh- SSH clientopenssl- Password generationfuse3- 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:
- Detect your Linux distribution
- Install required packages (restic, openssh, openssl, fuse3)
- Generate Ed25519 SSH key pair
- Upload public key to Hetzner (you'll enter password once)
- Create SSH config for easy access
- Generate strong encryption password
- Initialize encrypted repository on Hetzner Storagebox/SFTP Server
- 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
Option 2: Systemd Timer (Recommended)
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:
- Exclude unnecessary files:
restic backup /home \
--exclude '*.cache' \
--exclude '*.tmp' \
--exclude 'node_modules/' \
--exclude '.git/'
- Optimize compression (use larger pack size):
restic backup /home --pack-size 128
- Limit bandwidth if ISP throttles:
restic backup /home --limit-upload 10000 # 10 MB/s
- Skip directory scanning (if unchanged):
restic backup /home --no-scan
- 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
-
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)
-
Secure SSH Keys
- Keys stored in
/root/.ssh/(root only) - Use Ed25519 (modern, secure)
- Never copy private key elsewhere
- Permissions: 600 (private), 644 (public)
- Keys stored in
-
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"
- 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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Test your changes thoroughly
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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?
- Check TROUBLESHOOTING.md
- Review Restic Documentation
- Ask on Restic Forum
Security Issues?
- DO NOT open public issues for security vulnerabilities
- Email: security@wiotte.de
- Use GPG for sensitive communications
📊 Status
Used Restic Version: 0.17+
Tested On: Arch Linux (Garuda), Debian 12
Maintainer: Matteo