Automated Encrypted Backups with Restic on Ubuntu

Written by: Bagus Facsi Aginsa
Published at: 06 Jun 2026


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 sudo access
  • A directory of real data worth backing up (we will use /var/www and /etc as 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 nano or vim

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.