restic is a single-binary backup program that encrypts and deduplicates data on the client and writes it directly to local disks, SFTP servers or object storage. Because it speaks the S3 API, it is a simple way to keep an offsite copy of a server without running a second machine. In this tutorial you will back up an Ubuntu 24.04 server to an S3-compatible bucket with restic, include a database dump, schedule it with a systemd timer, apply a retention policy and restore files.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS (for example a CubePath VPS) with a non-root user with
sudoprivileges. - An S3-compatible object storage bucket in a different location from the server, with:
- the S3 endpoint URL, referred to as
your_s3_endpoint(for examples3.example.com), - an empty bucket, referred to as
your_bucket, - an access key and secret key with read and write access to that bucket.
- the S3 endpoint URL, referred to as
- A password manager or other safe place outside the server to store the repository password.
NoteIf you do not use object storage, restic also works over SFTP with any SSH server. Replace the repository string in Step 2 with
sftp:your_user@your_backup_server_ip:/srv/restic/web01; everything else in this guide stays the same.
Step 1 - Installing restic
restic is available in the Ubuntu repositories:
sudo apt update
sudo apt install restic
Check the installed version:
restic version
restic 0.16.4 compiled with go1.22.2 on linux/amd64
The Ubuntu package is fully usable for this guide. Newer releases are published as prebuilt binaries on the restic GitHub releases page if you need a feature added later; the repository format is compatible across these versions.
Step 2 - Storing the repository settings
restic reads its repository location, password and cloud credentials from environment variables. Keeping them in one file lets you use the same settings in your shell and in the systemd service later.
Create a configuration directory that only root can read:
sudo install -d -m 700 /etc/restic
Generate a random repository password:
openssl rand -base64 32 | sudo tee /etc/restic/password > /dev/null
sudo chmod 600 /etc/restic/password
Print it once and save it in your password manager. Without this password, the backups cannot be decrypted by anyone, including you:
sudo cat /etc/restic/password
Now create the environment file:
sudo nano /etc/restic/env
RESTIC_REPOSITORY=s3:https://your_s3_endpoint/your_bucket/web01
RESTIC_PASSWORD_FILE=/etc/restic/password
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
The path after the bucket name (web01) is a prefix inside the bucket, so several servers can share one bucket with separate repositories. The AWS_* variable names are used for every S3-compatible provider, not only AWS. Some providers also require a region; if yours does, add AWS_DEFAULT_REGION=your_region.
Protect the file, since it contains credentials:
sudo chmod 600 /etc/restic/env
Step 3 - Initializing the repository
The remaining commands run as root so restic can read every file. Open a root shell and load the settings into it:
sudo -i
set -a; source /etc/restic/env; set +a
set -a exports every variable defined while it is active, which is what restic needs. Now create the repository:
restic init
created restic repository 5a2c8e91f0 at s3:https://your_s3_endpoint/your_bucket/web01
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
If you see an access denied error instead, check the keys, the bucket name and the endpoint URL.
Step 4 - Creating your first backup
Create a list of paths to leave out:
nano /etc/restic/excludes
/home/*/.cache
/root/.cache
/var/www/*/cache
*.swp
Run the first backup of the directories that matter on a typical server:
restic backup --exclude-caches --exclude-file /etc/restic/excludes \
/etc /home /root /var/www
--exclude-caches skips directories that contain a standard CACHEDIR.TAG file. At the end, restic prints a summary:
Files: 48213 new, 0 changed, 0 unmodified
Dirs: 6120 new, 0 changed, 0 unmodified
Added to the repository: 912.482 MiB (905.117 MiB stored)
processed 48213 files, 2.140 GiB in 1:52
snapshot 3f1a2b4c saved
Run the same command again. This time nearly every file is listed as unmodified and only a few kilobytes are added, because restic only uploads data it has not seen before.
Backing up a database
Never back up the raw files of a running database. restic can read a dump from standard input and store it as a file in the snapshot, so nothing is written to the local disk. For MySQL or MariaDB:
mysqldump --single-transaction --routines --events --all-databases \
| restic backup --stdin --stdin-filename mysql-all.sql --tag database
For PostgreSQL, use sudo -u postgres pg_dumpall in place of mysqldump. On Ubuntu 24.04, root connects to MySQL and MariaDB through the Unix socket, so no password is needed.
Step 5 - Browsing and restoring snapshots
List the snapshots in the repository:
restic snapshots
ID Time Host Tags Paths
------------------------------------------------------------------------------
3f1a2b4c 2026-09-25 10:30:11 web01 /etc /home /root /var/www
9c0d4e1b 2026-09-25 10:34:02 web01 /etc /home /root /var/www
e7f1a0c3 2026-09-25 10:36:45 web01 database /mysql-all.sql
------------------------------------------------------------------------------
3 snapshots
List the files of one directory in the most recent file snapshot:
restic ls latest --path /etc /etc/nginx
--path /etc makes latest pick the newest snapshot that includes /etc, so the database snapshot is ignored.
Restore /etc/nginx into a temporary directory and compare it with the live copy:
restic restore latest --path /etc --target /tmp/restic-restore --include /etc/nginx
diff -r /etc/nginx /tmp/restic-restore/etc/nginx && echo "identical"
identical
restic recreates the full path under the target directory and restores ownership and permissions. To go back further in time, use a snapshot ID such as 9c0d4e1b instead of latest.
To restore the database dump, write it to standard output and pipe it where you need it:
restic dump latest --tag database /mysql-all.sql > /tmp/mysql-all.sql
Step 6 - Applying a retention policy
restic forget decides which snapshots to keep and --prune deletes the data only they used. Preview it first:
restic forget --dry-run --keep-daily 7 --keep-weekly 4 --keep-monthly 6
This keeps the latest 7 daily, 4 weekly and 6 monthly snapshots. restic applies the policy separately to each group of host and paths, so the database snapshots and the file snapshots each keep their own history. When the output looks right, run it for real:
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Step 7 - Scheduling backups with a systemd timer
A systemd service can load the same environment file you created in Step 2, so no wrapper script is needed. Each ExecStart line runs in order and the service stops at the first failure.
Create the service unit:
nano /etc/systemd/system/restic-backup.service
[Unit]
Description=restic backup to object storage
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
Environment=RESTIC_CACHE_DIR=/var/cache/restic
CacheDirectory=restic
ExecStart=/usr/bin/restic backup --exclude-caches --exclude-file /etc/restic/excludes /etc /home /root /var/www
ExecStart=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Nice=10
IOSchedulingClass=idle
CacheDirectory=restic makes systemd create /var/cache/restic, where restic keeps a local metadata cache that avoids downloading index data on every run. If you back up a database, add a line before the forget line that calls a small script running the mysqldump | restic backup --stdin pipeline from Step 4, because systemd does not interpret pipes in ExecStart.
Create the timer unit:
nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Nightly restic backup
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer and trigger one run to verify it works under systemd:
systemctl daemon-reload
systemctl enable --now restic-backup.timer
systemctl start restic-backup.service
journalctl -u restic-backup.service -n 30 --no-pager
The journal shows the backup summary, the forget report and Finished restic-backup.service. Check the next scheduled run with:
systemctl list-timers restic-backup.timer
Step 8 - Checking repository integrity
restic check verifies the repository structure. Adding --read-data-subset also downloads and verifies a portion of the actual data, which catches corruption in the storage without downloading everything each time:
restic check --read-data-subset=5%
The command lists each phase as it runs and ends with:
no errors were found
Run this monthly. Object storage providers may charge for the data downloaded by the check, so keep the percentage modest.
Leave the root shell when you are done:
exit
Troubleshooting
Fatal: unable to open config file: Stat: ... Access Denied: the keys do not have access to the bucket, or the endpoint or bucket name is wrong. Test the values with your provider's console.Fatal: wrong password or no key found:/etc/restic/passworddoes not match the repository. Restore the file from your password manager.unable to create lock in backend: repository is already locked: another restic process is running, or one was killed. When you are sure nothing is running, remove stale locks withrestic unlock.- The service fails with exit code 3: some files could not be read during the backup, for example because they were deleted mid-run. The snapshot was still saved; check the journal for the file names and exclude them if they are temporary.
Conclusion
Your server now uploads encrypted, deduplicated snapshots to object storage every night, keeps a defined history and verifies it can read the data back. That gives you the offsite copy of the 3-2-1 backup rule. Next, consider enabling object lock or versioning on the bucket if your provider supports it, so a compromised server cannot delete old backups, and pair restic with a fast local copy using BorgBackup for quicker restores.
