Losing data is not a question of if, but when. Hard drives fail, ransomware locks files, and accidental deletes happen to everyone. The difference between a minor inconvenience and a catastrophe comes down to one thing: whether you have a working backup.
This guide walks you through setting up automatic encrypted off-site backups with restic and systemd timers. By the end, you will have a fully automated backup system that runs on a schedule, encrypts your data before it leaves your machine, and stores it safely off-site. No manual intervention required.
Why restic and systemd? Restic handles encryption, deduplication, and snapshot management. Systemd timers handle scheduling with more flexibility than cron, including built-in retry logic and logging. Together, they give you a professional-grade backup solution that costs nothing but your time to set up.
Table of Contents
What Is restic and Why Pair It With systemd Timers?
Restic is an open-source backup tool that creates encrypted, deduplicated snapshots of your files. It encrypts everything locally before sending data to the backup repository, so your data is protected even if the storage provider gets compromised. Restic supports multiple backends: local drives, SFTP servers, Backblaze B2, Amazon S3, and more.
Systemd timers are the modern Linux replacement for cron jobs. They offer dependencies on other services, calendar-based or event-based scheduling, automatic logging through the journal, and the ability to catch up on missed runs with Persistent=true. Unlike cron, systemd timers can prevent overlapping runs natively and provide random delay options to spread load across multiple machines.
Pairing restic with systemd timers gives you encrypted backup automation that is reliable, observable, and self-documenting. Every run produces logs you can inspect with journalctl, and the service definitions themselves serve as documentation for what your backup does.
Prerequisites and Installation
Before you begin, make sure you have the following:
- A Linux machine with systemd (any modern distribution works)
- Restic installed on that machine
- A backup destination ready (SFTP server, Backblaze B2 bucket, or external drive)
- Root or sudo access for system-wide timers (or a user session for
systemctl --user) - SSH keys configured if using SFTP, or API keys if using B2
Install restic using your distribution’s package manager:
# Debian / Ubuntu
sudo apt install restic
# Fedora
sudo dnf install restic
# Arch Linux
sudo pacman -S restic
# macOS (with Homebrew)
brew install restic
Verify the installation completed successfully:
restic version
You should see the restic version number printed. If you want the latest features, you can also download the binary directly from the restic GitHub releases page.
Step 1: Initialize the Backup Repository
The backup repository is where restic stores all encrypted snapshots. You need to initialize it once before your first backup. First, set the repository location and password as environment variables:
export RESTIC_REPOSITORY="sftp:user@server:/path/to/repo"
export RESTIC_PASSWORD="your-strong-password-here"
For Backblaze B2, the setup looks slightly different:
export RESTIC_REPOSITORY="b2:your-bucket-name:/path"
export RESTIC_PASSWORD="your-strong-password-here"
export B2_ACCOUNT_ID="your-account-id"
export B2_ACCOUNT_KEY="your-account-key"
Now initialize the repository:
restic init
Restic will create the repository structure and encrypt it with your password. Choose a strong password that you will not lose. If you forget this password, your backups are permanently unrecoverable. There is no password reset for encrypted backups.
Store a copy of the password somewhere safe, like a password manager. Many users store the repository password in a separate encrypted file that systemd reads at runtime through an environment file.
Step 2: Create the Environment File
Storing credentials directly in systemd unit files is bad practice. Instead, use an environment file that systemd reads at runtime. Create the file at /etc/restic (system-wide) or ~/.config/restic (user-level):
sudo mkdir -p /etc/restic
sudo nano /etc/restic/env
Add your credentials to this file:
# For SFTP backup destination
RESTIC_REPOSITORY="sftp:user@server:/path/to/repo"
RESTIC_PASSWORD="your-strong-password-here"
# For Backblaze B2 (uncomment if using B2)
# B2_ACCOUNT_ID="your-account-id"
# B2_ACCOUNT_KEY="your-account-key"
Restrict permissions so only root can read the credentials:
sudo chmod 600 /etc/restic/env
sudo chown root:root /etc/restic/env
This environment file is referenced by the systemd service unit using the EnvironmentFile directive, which we will cover in the next step.
Step 3: Create the systemd Service Unit
The service unit defines what your backup does. It runs the actual restic backup command with all the right flags. Create the service file:
sudo nano /etc/systemd/system/restic-backup.service
Add the following configuration:
[Unit]
Description=Restic Backup Service
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=restic backup /home /etc /var/lib --verbose --one-file-system --exclude-caches
Restart=on-failure
RestartSec=300
Each line matters. Type=oneshot tells systemd this is a one-time task, not a long-running daemon. Wants=network-online.target and After=network-online.target ensure the network is up before the backup runs, which is essential for SFTP and B2 destinations.
The ExecStart line is where the real work happens. Adjust the paths (/home /etc /var/lib) to match what you want to back up. The --verbose flag ensures detailed logging. The --one-file-system flag prevents restic from crossing filesystem boundaries, which avoids accidentally backing up mounted drives or virtual filesystems.
Restart=on-failure with RestartSec=300 means if the backup fails, systemd will retry after a 5-minute delay. This handles transient network errors gracefully.
Step 4: Create the systemd Timer Unit
The timer unit controls when the backup service runs. Without a timer, your service file does nothing on its own. Create the timer:
sudo nano /etc/systemd/system/restic-backup.timer
Add the following configuration:
[Unit]
Description=Daily Restic Backup Timer
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=30m
[Install]
WantedBy=timers.target
OnCalendar=daily schedules the backup to run once per day at midnight. You can use other calendar expressions like OnCalendar=*-*-* 03:00:00 for a specific time, or OnCalendar=hourly for more frequent backups.
Persistent=true is important. If your machine was off or asleep when the timer was supposed to fire, systemd will run the missed backup when the machine boots back up. Without this, a missed schedule is simply skipped.
RandomizedDelaySec=30m adds up to 30 minutes of random delay to the scheduled time. This is critical if multiple machines back up to the same repository. Without random delays, all machines hit the repository simultaneously, causing lock conflicts. Forum users on r/selfhosted frequently report this exact problem when backing up several hosts to a shared repo.
Reload systemd to recognize the new unit files:
sudo systemctl daemon-reload
Step 5: Test and Enable the Backup
Never enable a timer without testing the service first. A misconfigured backup that “succeeds” without actually copying data is worse than no backup at all. Run the service manually to confirm it works:
sudo systemctl start restic-backup.service
Check the status and look for any errors:
sudo systemctl status restic-backup.service
For detailed logs, use journalctl to see exactly what restic did during the run:
sudo journalctl -u restic-backup.service -e
Look for lines indicating the snapshot was created successfully. You should see output like “snapshot abc12345 saved” with statistics about data added and total size.
Verify the snapshot exists in your repository:
sudo RESTIC_REPOSITORY="sftp:user@server:/path/to/repo"
RESTIC_PASSWORD="your-strong-password-here"
restic snapshots
Once you confirm the manual run works, enable and start the timer:
sudo systemctl enable restic-backup.timer
sudo systemctl start restic-backup.timer
Confirm the timer is scheduled by listing all active timers:
sudo systemctl list-timers --all
You should see restic-backup.timer listed with its next trigger time and last trigger result.
Step 6: Configure Retention With restic forget and prune
Without a retention policy, restic keeps every snapshot forever. Over time, your repository grows unbounded. A retention policy tells restic which old snapshots to keep and which to remove.
Many people confuse restic forget and restic prune. They do different things. restic forget marks snapshots for removal from the index but does not free disk space. restic prune actually removes the unreferenced data from the repository. You can combine both with restic forget --prune, which experienced users on the restic forum recommend for simplicity.
Create a separate service for cleanup:
sudo nano /etc/systemd/system/restic-prune.service
[Unit]
Description=Restic Prune Service
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --keep-yearly 1 --prune
Restart=on-failure
RestartSec=300
This retention policy keeps 7 daily snapshots, 4 weekly snapshots, 6 monthly snapshots, and 1 yearly snapshot. Everything else gets pruned. Adjust these numbers based on how much history you need and how much storage you have.
Create a timer that runs cleanup weekly instead of daily, since pruning is resource-intensive:
sudo nano /etc/systemd/system/restic-prune.timer
[Unit]
Description=Weekly Restic Prune Timer
[Timer]
OnCalendar=weekly
Persistent=true
RandomizedDelaySec=1h
[Install]
WantedBy=timers.target
Enable the prune timer:
sudo systemctl daemon-reload
sudo systemctl enable restic-prune.timer
sudo systemctl start restic-prune.timer
One warning: never run prune while a backup is in progress. The prune timer should be scheduled at a different time than the backup timer to avoid conflicts. Scheduling prune weekly and backup daily naturally separates them in most cases.
Step 7: Restore Files From a Backup
A backup you have never restored is a gamble. Every competitor guide we reviewed skips the restore process entirely, but this is the most important part of any backup strategy. Here is how to restore your data with restic.
First, list available snapshots to find the one you want to restore:
sudo bash -c 'source /etc/restic/env && restic snapshots'
You will see a list of snapshots with IDs, timestamps, and paths. Note the snapshot ID you want to restore.
To restore an entire snapshot to a directory:
sudo bash -c 'source /etc/restic/env && restic restore abc12345 --target /tmp/restore'
This restores the complete snapshot to /tmp/restore. You can then copy specific files from there to their original locations.
To restore a single file or directory from a snapshot, use the --include flag:
sudo bash -c 'source /etc/restic/env && restic restore abc12345 --target /tmp/restore --include /home/user/documents'
Only the specified path is restored, saving time and disk space.
If you do not know which snapshot contains the version you need, use the restic find command to search across all snapshots:
sudo bash -c 'source /etc/restic/env && restic find document.pdf'
This shows every snapshot that contains document.pdf along with the modification time for each copy. Pick the snapshot with the version you want and restore it.
To mount the entire repository as a filesystem for browsing (requires FUSE):
sudo bash -c 'source /etc/restic/env && restic mount /mnt/restic'
Now you can browse all snapshots as directories under /mnt/restic/snapshots/. Each snapshot appears as a folder containing the full file tree at that point in time. This is incredibly useful for finding the exact version of a file you need.
Always verify restored data after copying it back. Open files, check file sizes, and confirm integrity before relying on restored data in production.
Verifying Your Backups Actually Work
Running a backup successfully does not guarantee the data is usable. Repository corruption, partial uploads, and storage errors can all silently break your backups. Restic provides a built-in integrity check command.
Run restic check periodically to verify repository integrity:
sudo bash -c 'source /etc/restic/env && restic check'
This verifies that all data packs in the repository are intact and readable. For a deeper check that actually downloads and verifies data:
sudo bash -c 'source /etc/restic/env && restic check --read-data'
The --read-data flag is thorough but slow and uses bandwidth. Schedule it weekly or monthly, not daily.
For monitoring, consider integrating with a service like healthchecks.io. Add a curl call at the end of your backup ExecStart that pings a healthchecks endpoint on success. If the backup fails or does not run, healthchecks alerts you after the grace period expires. This catches silent failures where the timer is enabled but the service errors out.
Troubleshooting Common restic and systemd Issues
Even with a correct setup, things go wrong. Here are the most common issues reported across forums and how to fix them.
Failed to Connect to Bus: Operation Not Permitted
This error appears when running systemctl --user commands in environments without a proper D-Bus session, such as SSH sessions without lingering enabled, or cron contexts. Fix it by enabling lingering for your user:
sudo loginctl enable-linger yourusername
This allows user services to run even when you are not logged in. If you continue having issues, consider using system-wide timers (without --user) instead.
Lock Conflicts When Multiple Hosts Back Up to the Same Repository
Restic uses locks to prevent concurrent operations from corrupting the repository. When two machines try to back up simultaneously, one will fail with a lock error. The fix is RandomizedDelaySec in your timer unit, which spreads backup start times across your machines.
Set different delay ranges for each host:
# Host 1 timer
RandomizedDelaySec=30m
# Host 2 timer
RandomizedDelaySec=1h
This naturally desynchronizes backup runs across machines sharing a repository.
How and When to Use restic unlock Safely
When a backup is interrupted (power loss, network drop, kill signal), restic may leave a stale lock in the repository. Subsequent backups fail because restic thinks another operation is still running. Use restic unlock to clear stale locks:
sudo bash -c 'source /etc/restic/env && restic unlock'
Running unlock before every backup is dangerous. Forum users on r/selfhosted warn that blindly unlocking can interrupt a legitimately running operation on another machine. Only run unlock when you are certain no backup or prune operation is in progress anywhere. Check first with restic list locks to see if locks exist and are stale.
Timer Is Enabled But Backups Never Run
Check that the timer is actually started, not just enabled. Enabling a timer makes it persist across reboots, but you must also start it for the current session:
sudo systemctl start restic-backup.timer
sudo systemctl list-timers restic-backup.timer
Verify the “NEXT” column shows a future time. If it shows a past time with “n/a” in the next column, the timer is not running.
Permission Denied Errors on Backup Paths
If restic reports permission denied for files it should be able to read, make sure the service is running as the right user. Add User=root to the [Service] section if backing up system files, or specify the appropriate username for user data.
Frequently Asked Questions
How do I automate restic backup with systemd?
Create a systemd service unit with Type=oneshot and an ExecStart running your restic backup command. Then create a matching timer unit with OnCalendar set to your desired schedule. Enable and start the timer with systemctl enable and systemctl start. The service runs automatically on the timer’s schedule.
How to set up restic backup to run daily?
Set OnCalendar=daily in your systemd timer unit file, enable Persistent=true so missed runs are caught up after downtime, and use RandomizedDelaySec to prevent lock conflicts with other machines. Enable the timer with sudo systemctl enable restic-backup.timer and start it with sudo systemctl start restic-backup.timer.
How do I run restic unlock safely?
First check for existing locks with restic list locks. Only run restic unlock when you are certain no backup or prune operation is in progress on any machine sharing the repository. Running unlock before every backup is dangerous because it can interrupt legitimate operations on other hosts. Use it only to clear stale locks from interrupted runs.
How do I restore files from restic backup?
List snapshots with restic snapshots to find the ID you need. Run restic restore followed by the snapshot ID and a target path to restore everything. To restore specific files, add u002du002dinclude followed by the file path. You can also use restic find to search for a specific file across all snapshots before restoring.
How do I configure restic retention policies?
Use restic forget with flags like u002du002dkeep-daily 7, u002du002dkeep-weekly 4, u002du002dkeep-monthly 6, and u002du002dkeep-yearly 1. Add u002du002dprune to the forget command to actually remove unreferenced data from the repository in one step. Schedule this as a separate weekly systemd service to avoid running prune during daily backups.
What is the 3-2-1 backup strategy?
The 3-2-1 strategy means keeping 3 copies of your data, on 2 different types of storage media, with 1 copy stored off-site. Restic with systemd timers naturally supports this because you can back up to multiple destinations (local drive, SFTP server, and cloud storage like B2) from a single machine with different service configurations.
How do I use restic with SFTP or rsync.net?
Set RESTIC_REPOSITORY to sftp:user@host:/path/to/repo in your environment file. Ensure SSH key authentication is set up so the backup runs without password prompts. For rsync.net specifically, use your rsync.net subdomain as the host and your storage path as the repository location. Initialize the repository with restic init before your first backup.
How do I prevent systemd timers from running simultaneously?
Use RandomizedDelaySec in your timer unit to add a random delay to each scheduled run. For multiple machines backing up to the same repository, use different delay ranges on each host. Systemd also prevents the same service from running twice because Type=oneshot services will not start again if already running.
Conclusion
You now have a complete system for automatic encrypted off-site backups with restic and systemd timers. Your backups run on schedule, encrypt data before it leaves your machine, deduplicate to save storage, and follow a retention policy that keeps history without growing unbounded.
The next step is critical: test a full restore. Pick a snapshot, restore a file, and confirm it works. A backup you cannot restore is not a backup at all. Make restore testing a habit, not an afterthought.
Consider extending this setup with health monitoring (healthchecks.io), multiple backup destinations for a true 3-2-1 strategy, and periodic restic check --read-data runs to verify repository integrity. Your data deserves more than hope. It deserves a system that actually works when you need it most.