Backups are one of those things everyone agrees are important, yet most servers run for years without a single tested restore. The usual story is a tangle of tar commands wrapped in a shell script, dumped to an external disk, and forgotten. Then one day a database directory gets deleted, someone reaches for the backup, and discovers the archive is six months old or, worse, corrupted.
This tutorial fixes that. You will set up Restic, a modern backup program that handles encryption, deduplication, and snapshotting out of the box. By the end you will have a server that backs itself up automatically every night, keeps a sensible history of snapshots, and lets you restore any file in seconds. Best of all, every byte that leaves your machine is encrypted, so you can safely store backups on a remote server or cheap object storage without worrying about who can read them.
This guide is for developers, sysadmins, and DevOps engineers who run at least one Ubuntu server and want a backup strategy they can actually trust. You only need basic command-line skills to follow along.
Why Restic Instead of a Shell Script
Before touching commands, it helps to understand what Restic does differently. Three concepts matter.
Snapshots. Each time Restic runs, it records the state of your files as a snapshot, tagged with a timestamp. You can list every snapshot and restore the exact state of your data from any point in time. This is closer to how a version control system works than a plain archive.
Deduplication. Restic splits your files into variable-sized chunks and only stores each unique chunk once. If you back up a 2 GB directory every night and only a few files change, each new snapshot adds just a few megabytes, not another 2 GB. Over weeks this saves an enormous amount of space.
Encryption. The entire repository is encrypted with AES-256 before anything is written to disk. The encryption key is derived from a password you choose. Without that password, the backup is unreadable noise. This is what makes it safe to push backups to a server you do not fully control.
A “repository” is simply the place Restic stores its data. It can be a local directory, an external drive, an SFTP server, or an S3-compatible bucket. The commands stay the same regardless of where the repository lives, which is one of the nicest things about the tool.
Prerequisites
- Ubuntu 20.04, 22.04, or 24.04 with
sudoaccess - A directory of real data worth backing up (we will use
/var/wwwand/etcas examples) - Somewhere to store the backup: a second disk, an SFTP server, or an S3 bucket
- Basic familiarity with the terminal and editing files with
nanoorvim
For this walkthrough the main repository will live in a local directory to keep things simple, and then we will point the same setup at a remote SFTP target so you can see how an offsite copy works. If you want a network share as a target instead, the NFS server setup guide pairs well with this approach.
Step 1: Install Restic
Restic is packaged in the Ubuntu repositories, so installation is a single command:
sudo apt update
sudo apt install -y restic
Confirm it installed and check the version:
restic version
You should see output similar to this:
restic 0.16.4 compiled with go1.22.2 on linux/amd64
The version in the Ubuntu repository can lag behind upstream. That is fine for most use, but if you want the newest release you can let Restic update itself once it is installed:
sudo restic self-update
This downloads the latest official binary and replaces the installed one. Run it now so you are on a current version.
Step 2: Initialize the Repository
A repository must be initialized once before you can store anything in it. First, decide where it lives. We will use a directory on a second mount point at /mnt/backup, which you should adjust to your own disk:
sudo mkdir -p /mnt/backup/restic-repo
Restic needs two pieces of information for every operation: the repository location and the password that unlocks it. You pass the location with the -r flag and the password through the RESTIC_PASSWORD environment variable or an interactive prompt.
Initialize the repository:
sudo restic -r /mnt/backup/restic-repo init
You will be asked to set a password:
enter password for new repository:
enter password again:
created restic repository 1a2b3c4d at /mnt/backup/restic-repo
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
Read that last line twice. The password is not recoverable. If you lose it, your backups are gone for good. Store it in a password manager right now, before you go any further.
Step 3: Take Your First Snapshot
With the repository ready, backing up is a single command. Let’s snapshot the web root and system configuration:
sudo restic -r /mnt/backup/restic-repo backup /var/www /etc
Restic scans the files, chunks them, and writes the snapshot:
repository 1a2b3c4d opened (version 2, compression level auto)
no parent snapshot found, will read all files
Files: 1284 new, 0 changed, 0 unmodified
Dirs: 198 new, 0 changed, 0 unmodified
Added to the repository: 184.231 MiB (61.402 MiB stored)
processed 1284 files, 184.102 MiB in 0:04
snapshot 9f8e7d6c saved
Notice the difference between “Added to the repository” and “stored”. Restic compressed and deduplicated the data, so 184 MiB of files became 61 MiB on disk.
Run the same command a second time. Because nothing changed, the snapshot is nearly instant and adds almost no data:
Files: 0 new, 0 changed, 1284 unmodified
Added to the repository: 0 B (0 B stored)
That is deduplication doing its job. List your snapshots to confirm both are recorded:
sudo restic -r /mnt/backup/restic-repo snapshots
ID Time Host Tags Paths
---------------------------------------------------------
9f8e7d6c 2026-06-06 01:12:04 web-01 /etc, /var/www
3c2b1a0f 2026-06-06 01:14:39 web-01 /etc, /var/www
Step 4: Restore Files
A backup you have never restored from is just a hope, not a plan. Let’s prove it works.
To browse what a snapshot contains, mount it as a regular filesystem (this needs the fuse package, which is usually already present):
sudo mkdir -p /mnt/restic-view
sudo restic -r /mnt/backup/restic-repo mount /mnt/restic-view
While that command runs, open a second terminal and look inside:
ls /mnt/restic-view/snapshots/latest/var/www
Every snapshot appears as a folder you can browse and copy from. Press Ctrl+C in the first terminal to unmount when you are done.
For a direct restore without mounting, use the restore command. This recovers the whole /etc tree from a specific snapshot into a target directory:
sudo restic -r /mnt/backup/restic-repo restore 9f8e7d6c \
--target /tmp/restore-test --include /etc
Check the result:
ls /tmp/restore-test/etc/nginx
Restoring into a separate target directory like /tmp/restore-test rather than over the live files is the safe habit. You inspect what came back first, then copy the specific files you actually need into place.
Step 5: Manage Snapshot Retention
If you back up every night and never clean up, the repository grows forever. Restic separates this into two steps: forget removes snapshots from the index according to a policy, and prune reclaims the disk space the forgotten snapshots were using.
A common, sensible policy keeps the last 7 daily, 4 weekly, and 6 monthly snapshots:
sudo restic -r /mnt/backup/restic-repo forget \
--keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
The --prune flag at the end tells Restic to immediately run the cleanup after forgetting. The output shows what it kept and removed:
Applying Policy: keep 7 daily, 4 weekly, 6 monthly snapshots
keep 9 snapshots:
ID Time Host Reason
...
remove 3 snapshots:
...
3 snapshots have been removed, running prune
Pruning rewrites pack files and can take a while on a large repository, so it is usually run once a day rather than after every single backup.
Step 6: Automate It with a systemd Timer
Manual backups get forgotten. The reliable way to schedule them on Ubuntu is a systemd service paired with a timer. This is cleaner than cron because you get proper logging through journalctl and the service definition documents itself.
First, store the repository settings in a file so you do not repeat them. Create /etc/restic/env:
sudo mkdir -p /etc/restic
sudo nano /etc/restic/env
Add the repository path and the password:
RESTIC_REPOSITORY=/mnt/backup/restic-repo
RESTIC_PASSWORD=your-strong-repository-password
Because this file holds the password in plain text, lock down its permissions so only root can read it:
sudo chmod 600 /etc/restic/env
Next, create the service unit at /etc/systemd/system/restic-backup.service:
sudo nano /etc/systemd/system/restic-backup.service
[Unit]
Description=Restic backup of system data
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=/usr/bin/restic backup /var/www /etc --tag automated
ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
The EnvironmentFile line loads the repository and password, so the ExecStart command stays short. Type=oneshot tells systemd this is a task that runs to completion rather than a long-lived daemon. The ExecStartPost line applies the retention policy right after each successful backup.
Now create the timer at /etc/systemd/system/restic-backup.timer:
sudo nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Run Restic backup nightly
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
OnCalendar=*-*-* 02:30:00 runs the backup every day at 2:30 AM. Persistent=true means that if the machine was powered off at 2:30, the backup runs as soon as it boots up again, so you never silently skip a night.
Reload systemd and enable the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
Confirm the timer is scheduled:
systemctl list-timers restic-backup.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
2026-06-07 02:30:00 UTC 16h left - - restic-backup.timer restic-backup.service
Test the whole pipeline immediately without waiting for 2:30 AM by triggering the service directly:
sudo systemctl start restic-backup.service
Then read the logs to confirm it ran cleanly:
journalctl -u restic-backup.service --no-pager -n 20
Step 7: Add an Offsite Copy over SFTP
A backup on the same machine, or even the same building, does not protect you against fire, theft, or a failed RAID controller. The fix is an offsite copy. Restic talks to any SSH server out of the box, so an SFTP repository needs no extra software on the remote side.
Assuming you have a backup server reachable at 203.0.113.50 with a user named backup, set up key-based SSH access first so the automated job runs without a password prompt:
sudo ssh-keygen -t ed25519 -f /root/.ssh/restic_key -N ""
sudo ssh-copy-id -i /root/.ssh/restic_key.pub [email protected]
Initialize a second repository on the remote host:
sudo restic -r sftp:[email protected]:/srv/restic-repo init
The only thing that changed is the repository string: sftp:user@host:/path. Every other command (backup, restore, snapshots, forget) works identically against this remote repository. You can run a second timer pointed at the SFTP target, or back up to both locations, giving you a fast local copy for quick restores and an offsite copy for disaster recovery.
If you would rather push to S3-compatible object storage, the repository string becomes s3:s3.amazonaws.com/your-bucket and you supply AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the same environment file. The workflow does not change at all.
Common Mistakes and Troubleshooting
“wrong password or no key found”. This means the RESTIC_PASSWORD does not match what the repository was initialized with. Double-check the environment file for trailing spaces or a stray newline. There is no recovery if the original password is truly lost.
The repository is locked. If a backup is interrupted, Restic may leave a stale lock and later runs fail with “repository is already locked”. Confirm no Restic process is running, then clear it:
sudo restic -r /mnt/backup/restic-repo unlock
Backing up files that change while reading. Backing up a live database directory can capture an inconsistent state. For databases, dump first and back up the dump. For PostgreSQL, generate the dump with pg_dump (see the PostgreSQL setup guide) into a directory that Restic then snapshots.
Permission denied during restore. Restoring as a different user than the one who created the files can drop ownership. Run restores with sudo and verify ownership afterward with ls -l.
Disk fills up anyway. If the repository keeps growing, your forget policy is probably not running, or you forgot the --prune flag. Remember that forget without --prune only removes the index entry, it does not free the space.
Best Practices
Verify the repository regularly. Corruption is rare but possible. Run an integrity check weekly, reading a random subset of the actual data:
sudo restic -r /mnt/backup/restic-repo check --read-data-subset=5%
Follow the 3-2-1 rule. Keep at least three copies of your data, on two different types of media, with one copy offsite. The local plus SFTP setup above already gets you most of the way there.
Test restores on a schedule, not just when disaster strikes. Put a calendar reminder to restore a random file once a month. A backup is only real once you have recovered from it.
Protect the password file. The /etc/restic/env file holds the keys to your encrypted backups, so treat the whole server with care. Host-level hardening such as a firewall and intrusion prevention, like the setup in the Fail2ban brute force protection guide, keeps attackers away from it in the first place.
Monitor that backups actually ran. A silent failure is the worst kind. Have the systemd service send its exit status to a monitoring system, or at minimum review journalctl -u restic-backup.service periodically.
Conclusion
You now have a backup system that is encrypted, deduplicated, automated, and tested. You installed Restic, initialized a repository, took and restored snapshots, set a retention policy, and wired the whole thing into a nightly systemd timer with an offsite SFTP copy. That is a setup you can genuinely rely on when something goes wrong.
From here, a few directions are worth exploring. Point a second repository at S3-compatible object storage for cheap, durable offsite storage. Add a pre-backup hook that dumps your databases so they are captured consistently. And whatever you do, schedule that monthly restore test, because the only backup that counts is the one you have proven you can bring back.