This script automates encrypted, deduplicated backups of local directories to a remote SFTP server (such as a Hetzner Storage Box) using restic
.
- Client-Side Encryption: All data is encrypted on your server before being uploaded, ensuring zero-knowledge privacy from the storage provider.
- Deduplication & Compression: Saves significant storage space by only storing unique data blocks and applying compression.
- Snapshot-Based Backups: Creates point-in-time snapshots, allowing you to easily browse and restore files from any backup date.
- Advanced Retention Policies: Sophisticated rules to automatically keep daily, weekly, monthly, and yearly snapshots.
- Unified Configuration: All settings are managed in a single, easy-to-edit
restic-backup.conf
file. - Notification Support: Sends detailed success, warning, or failure notifications to ntfy, Discord, Slack, and Microsoft Teams.
- Flexible File Exclusions: Exclude files and directories using either a dedicated exclusion file or by listing patterns directly in the configuration.
- System Friendly: Uses
nice
andionice
to minimize CPU and I/O impact during backups. - Multiple Operation Modes: Supports standard backups, dry runs, integrity checks, difference summaries, and a safe, interactive restore mode.
- Concurrency Control & Logging: Prevents multiple instances from running simultaneously and handles its own log rotation.
- Pre-run Validation: Performs checks for required commands and repository connectivity before execution.
- Cron Job Monitoring: Optional integration with Healthchecks.io for alerts if a backup job fails to run on schedule.
For those familiar with setting up backup scripts, here is a fast track to get you up and running.
-
Download Files:
mkdir -p /root/scripts/backup && cd /root/scripts/backup curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.sh curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.conf curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-excludes.txt chmod +x restic-backup.sh
-
Edit Configuration:
- Modify
restic-backup.conf
with your repository details, source paths, and password file location. - Set secure permissions:
chmod 600 restic-backup.conf
.
- Modify
-
Create Password & Initialize:
# Create the password file (use a strong password) echo 'your-very-secure-password' | sudo tee /root/.restic-password sudo chmod 400 /root/.restic-password # Initialize the remote repository sudo ./restic-backup.sh --init
-
Run First Backup & Schedule:
# Run your first backup with verbose output sudo ./restic-backup.sh --verbose # Set up a recurring schedule with the interactive wizard sudo ./restic-backup.sh --install-scheduler
sudo ./restic-backup.sh
- Run a standard backup silently (suitable for cron).sudo ./restic-backup.sh --verbose
- Run with live progress and detailed output.sudo ./restic-backup.sh --dry-run
- Preview changes without creating a new snapshot.sudo ./restic-backup.sh --check
- Verify repository integrity by checking a subset of data.sudo ./restic-backup.sh --check-full
- Run a full check verifying all repository data.sudo ./restic-backup.sh --test
- Validate configuration, permissions, and SSH connectivity.sudo ./restic-backup.sh --fix-permissions --test
- Run tests and interactively auto-correct insecure file permissions.sudo ./restic-backup.sh --install-scheduler
- Run the interactive wizard to set up an automated backup schedule (systemd/cron).sudo ./restic-backup.sh --uninstall-scheduler
- Remove a schedule created by the wizard.sudo ./restic-backup.sh --restore
- Start the interactive restore wizard.sudo ./restic-backup.sh --background-restore <snapshot> <dest>
- Restore in the background (non-blocking).sudo ./restic-backup.sh --sync-restore <snapshot> <dest>
- Restore in a cronjob (helpful for 3-2-1 backup strategy).sudo ./restic-backup.sh --forget
- Manually apply the retention policy and prune old data.sudo ./restic-backup.sh --diff
- Show a summary of changes between the last two snapshots.sudo ./restic-backup.sh --stats
- Display repository size, file counts, and stats.sudo ./restic-backup.sh --unlock
- Forcibly remove stale locks from the repository.sudo ./restic-backup.sh --snapshots
- List all available snapshots in the repository.sudo ./restic-backup.sh --snapshots-delete
- Permanently delete specific snapshots.sudo ./restic-backup.sh --init
- (One-time setup) Initialize the remote repository.sudo ./restic-backup.sh --help
- Displays help and all the flags.
Default log location:
/var/log/restic-backup.log
Script provides three distinct modes for restoring data, each designed for a different scenario.
This is an interactive wizard for guided restores. It is the best option when you are at the terminal and need to find and recover specific files or directories.
- Best for: Visually finding and restoring specific files or small directories.
- Process: - Lists available snapshots for you to choose from. - Asks for a destination path. - Performs a "dry run" to show you what will be restored before making any changes. - Requires your confirmation before proceeding with the actual restore.
Usage:
sudo ./restic-backup.sh --restore
This mode is designed for restoring large amounts of data (e.g., a full server recovery) without needing to keep your terminal session active.
- Best for: Large, time-consuming restores or recovering data over a slow network connection.
- How it works:
- This command is non-interactive. You must provide the snapshot ID and destination path as arguments directly on the command line.
- The restore job is launched in the background, immediately freeing up terminal.
- All output is saved to a log file in
/tmp/
. - A success or failure notification (via ntfy, Discord, etc.) upon completion.
Usage:
# Restore the latest snapshot to a specific directory in the background
sudo ./restic-backup.sh --background-restore latest /mnt/disaster-recovery
# Restore a specific snapshot by its ID
sudo ./restic-backup.sh --background-restore a1b2c3d4 /mnt/disaster-recovery
This mode runs the restore in the foreground and waits for it to complete before exiting. It's a reliable, non-interactive way to create a complete, consistent copy of backup data.
- Best for: Creating a secondary copy of backup (for example, via a cron job) on another server (for a 3-2-1 strategy) or for use in any automation where subsequent steps depend on the restore being finished.
- How it works:
- This command is non-interactive and requires the snapshot ID and destination path as command-line arguments.
- It runs as a synchronous (blocking) process. When a cron job executes the command, the job itself will not finish until the restore is 100% complete.
- This guarantees the data copy is finished before any other commands are run or the cron job is marked as complete.
Usage:
# On a second server, pull a full copy of the latest backup
sudo ./restic-backup.sh --sync-restore latest /mnt/local-backup-copy
# On your secondary server, run a sync-restore every day at 5:00 AM.
0 5 * * * /path/to/your/script/restic-backup.sh --sync-restore latest /path/to/local/restore/copy >> /var/log/restic-restore.log 2>&1
# Can also be used in a script to ensure a process runs only after a restore
sudo ./restic-backup.sh --sync-restore latest /srv/app/data && systemctl restart my-app
The script uses specific exit codes for different failures to help with debugging automated runs.
- Exit Code
1
: A fatal configuration error, such as a missingrestic-backup.conf
file or required variable. - Exit Code
5
: Lock contention; another instance of the script is already running. - Exit Code
10
: A required command (likerestic
orcurl
) is not installed. - Exit Code
11
: TheRESTIC_PASSWORD_FILE
cannot be found. - Exit Code
12
: The script cannot connect to or access the Restic repository. - Exit Code
13
: A source directory inBACKUP_SOURCES
does not exist or is not readable. - Exit Code
14
: TheEXCLUDE_FILE
is not readable. - Exit Code
15
: TheLOG_FILE
is not writable. - Exit Code
20
: Therestic init
command failed.
All files should be placed in a single directory (e.g., /root/scripts/backup
).
/root/scripts/backup/
├── restic-backup.sh (main script)
├── restic-backup.conf (settings and credentials)
└── restic-excludes.txt (patterns to exclude from backup)
Follow these steps to get the backup system running from scratch.
First, ensure the required tools are installed.
The script relies on several command-line tools to function correctly. Most are standard utilities, but you should ensure they are all present on your system.
You can install all required packages with a single command.
On Debian or Ubuntu:
sudo apt-get update && sudo apt-get install -y restic jq gnupg curl bzip2 util-linux coreutils less
On CentOS, RHEL, or Fedora:
sudo dnf install -y restic jq gnupg curl bzip2 util-linux coreutils less
You could also download and install the latest version of restic
.
Note: While restic
can be installed from your system's package manager, it is often an older version. It is recommended to install it manually or allow the script's built-in auto-updater to fetch the latest official version for you.
# Find your architecture (e.g., x86_64 or aarch64)
uname -m
# Download the latest binary for your architecture from the Restic GitHub page
# Go to the Restic GitHub releases page to find the URL for the latest version:
# https://github.com/restic/restic/releases
# Download the latest binary for your architecture (replace URL with the one you found)
curl -LO <URL_of_latest_restic_linux_amd64.bz2>
# Unzip, make executable, and move to your path
bunzip2 restic_*.bz2
chmod +x restic_*
sudo mv restic_* /usr/local/bin/restic
Package | Required For |
---|---|
restic |
The core backup tool used for all repository operations (backup, restore, check, forget). |
jq |
Parsing JSON for diff function to show difference between last two snapshots. |
curl |
Sending notifications to ntfy/Discord and fetching the latest version information from the GitHub API. |
bzip2 |
Decompressing the restic binary when using the auto-install/update feature. |
gnupg |
Provides gpg for verifying the PGP signature of the restic binary during auto-updates. |
util-linux |
Provides flock for preventing concurrent script runs and ionice for setting I/O priority. |
coreutils |
Provides essential commands used throughout the script, such as date , grep , sed , chmod , mv , and mktemp . |
less |
Paging through the list of files during an interactive restore (--restore mode). |
The most reliable way for the script to connect to a remote server is via an SSH config file.
-
Generate a root SSH key if one doesn't already exist:
sudo ssh-keygen -t ed25519
(Press Enter through all prompts).
-
Add your public key to the remote server's authorized keys. For a Hetzner Storage Box, you can paste the contents of
sudo cat /root/.ssh/id_ed25519.pub
into the control panel. -
Create an SSH config file to define an alias for your connection:
# Open the file in an editor sudo nano /root/.ssh/config
-
Add the following content, adjusting the details for your server:
Host storagebox HostName u123456.your-storagebox.de User u123456-sub4 Port 23 IdentityFile /root/.ssh/id_ed25519 ServerAliveInterval 60 ServerAliveCountMax 240
-
Set secure permissions and test the connection:
sudo chmod 600 /root/.ssh/config # This command should connect without a password and print "/home" sudo ssh storagebox pwd
-
Create your script directory:
mkdir -p /root/scripts/backup && cd /root/scripts/backup
-
Download the script, configuration, and excludes files from the repository:
# Download the main script curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.sh # Download the configuration template curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-backup.conf # Download the excludes list curl -LO https://raw.githubusercontent.com/buildplan/restic-backup-script/refs/heads/main/restic-excludes.txt
-
Make the script executable:
chmod +x restic-backup.sh
-
Set secure permissions for your configuration file:
chmod 600 restic-backup.conf
-
Edit
restic-backup.conf
andrestic-excludes.txt
to specify your repository path, source directories, notification settings, and exclusion patterns.
All script behavior is controlled by the restic-backup.conf
file. Below is an overview of the key settings available.
RESTIC_REPOSITORY
: The connection string for your remote storage.RESTIC_PASSWORD_FILE
: The absolute path to the file containing your repository's encryption password.BACKUP_SOURCES
: A list of local directories to back up. Use Bash array syntax("/path/one" "/path/two")
to handle spaces correctly.
You can define how many snapshots to keep for various timeframes. The script will automatically remove older snapshots that fall outside these rules.
KEEP_LAST
: Number of the most recent snapshots to keep.KEEP_DAILY
: Number of daily snapshots to keep.KEEP_WEEKLY
: Number of weekly snapshots to keep.KEEP_MONTHLY
: Number of monthly snapshots to keep.KEEP_YEARLY
: Number of yearly snapshots to keep.
The script can send detailed status notifications to multiple services. Each can be enabled or disabled individually.
NTFY_ENABLED
: Set totrue
to enable ntfy notifications.DISCORD_ENABLED
: Set totrue
to enable Discord notifications.SLACK_ENABLED
: Set totrue
to enable Slack notifications.TEAMS_ENABLED
: Set totrue
to enable Microsoft Teams notifications.- You must also provide the corresponding
_URL
and_TOKEN
for each service you enable.
You have two ways to exclude files and directories from your backups:
EXCLUDE_FILE
: Point this to a text file (likerestic-excludes.txt
) containing one exclusion pattern per line.EXCLUDE_PATTERNS
: A space-separated list of patterns to exclude directly in the configuration file (e.g.,*.tmp *.log
).
LOW_PRIORITY
: Set totrue
to run the backup with lower CPU (nice
) and I/O (ionice
) priority, minimizing impact on other services.CHECK_AFTER_BACKUP
: Set totrue
to automatically run a repository integrity check after each successful backup.PRUNE_AFTER_FORGET
: Set totrue
to automatically prune the repository after applying the retention policy, which frees up storage space.
Before the first backup, you need to create the repository password file and initialize the remote repository.
-
Create the password file. This stores the encryption key for your repository. Guard this file carefully!
# Replace 'your-very-secure-password' with a strong, unique password echo 'your-very-secure-password' | sudo tee /root/.restic-password # Set secure permissions sudo chmod 400 /root/.restic-password
-
Initialize the repository. Run the script with the
--init
flag:# Navigate to your script directory cd /root/scripts/backup # Run the initialization sudo ./restic-backup.sh --init
The easiest and most reliable way to schedule your backups is to use the script's built-in interactive wizard. It will guide you through creating and enabling either a modern systemd timer
(recommended) or a traditional cron job
.
-
Navigate to your script directory:
cd /root/scripts/backup
-
Run the scheduler installation wizard:
sudo ./restic-backup.sh --install-scheduler
Follow the on-screen prompts to choose your preferred scheduling system and frequency. The script will handle creating all the necessary files and enabling the service for you.
If you prefer to manage the schedule manually instead of using the wizard, you can edit the root crontab directly.
To run the backup automatically, edit the root crontab.
-
Open the crontab editor:
sudo crontab -e
-
Add the following lines to schedule your backups and maintenance.
# Define a safe PATH that includes the location of restic PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # Run the Restic backup every day at 3:00 AM 0 3 * * * /root/scripts/backup/restic-backup.sh > /dev/null 2>&1 # Run the retention/prune job every Sunday at 4:00 AM 0 4 * * 0 /root/scripts/backup/restic-backup.sh --forget > /dev/null 2>&1 # Cron job for a monthly full check (e.g., first Sunday of the month at 3 AM) 0 3 * * 0 [ $(date +\%d) -le 07 ] && /root/scripts/backup/restic-backup.sh --check-full > /dev/null 2>&1
For prune job in your
restic-backup.conf
, setPRUNE_AFTER_FORGET=true
.
For more details on how forget flag work, see the official Restic documentation on removing snapshots.
Redirecting output to/dev/null
is recommended, as the script handles its own logging and notifications.