Backup & Disaster Recovery

Back up a Linux server with restic to S3-compatible object storage

Encrypted, deduplicated restic backups to an S3-compatible bucket: repository setup, retention, integrity checks, restores and a systemd timer.

Published
Reading time
7 min read

restic is an open-source backup program that encrypts everything on the client and stores deduplicated snapshots in a repository, which can live in any S3-compatible bucket. This guide sets up restic on a Debian or Ubuntu server, runs a first backup, adds a retention policy and integrity checks, tests a restore and then automates the whole thing with a systemd timer.

It is most useful on servers where nobody else takes backups for you. A self-managed VPS is the usual case: on a Skyline Cloud VPS in Riyadh, for example, the platform does not take image snapshots or server backups today, so protecting the data inside the server is part of your own plan.

Before you start

  • A bucket and an access key on your object storage provider. Use a key that can reach only this bucket, not an account-wide key.
  • The endpoint URL of the provider, for example https://s3.example.com.
  • A restic version you know. The current upstream release is 0.19.1 (5 July 2026). The Debian archive carries restic 0.18.0 in Debian 13 (trixie) and 0.14.0 in Debian 12 (bookworm). The commands below are standard ones from the current documentation; if you run an older package, confirm options with restic help <command>. The project's official binaries can update themselves with restic self-update; a package installed with apt is updated by apt instead.
sudo apt update
sudo apt install restic
restic version

Store the secrets properly

restic needs two kinds of secret: the repository password, which encrypts your data, and the bucket credentials. Keep both in root-only files rather than in shell history or scripts. The restic documentation is blunt about the password: losing it means your data is irrecoverably lost. Save a copy in your password manager or another safe place off the server.

sudo install -d -m 700 /etc/restic
sudo sh -c 'head -c 32 /dev/urandom | base64 > /etc/restic/password'
sudo chmod 600 /etc/restic/password

Then create /etc/restic/env (mode 600) with the repository location and credentials. restic reads RESTIC_REPOSITORY and RESTIC_PASSWORD_FILE from the environment, and the S3 backend reads the standard AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables:

RESTIC_REPOSITORY=s3:https://s3.example.com/my-server-backups
RESTIC_PASSWORD_FILE=/etc/restic/password
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key

For non-Amazon endpoints restic uses path-style bucket access by default. If your provider needs a region, set AWS_DEFAULT_REGION or pass -o s3.region=...; without it restic assumes us-east-1.

Create the repository and run a first backup

Load the file into a root shell and initialise the repository once:

sudo -i
set -a; . /etc/restic/env; set +a
restic init
restic backup --one-file-system --exclude-caches /etc /home /var/www /var/backups
restic snapshots

--one-file-system stops restic from wandering into other mounted file systems, and --exclude-caches skips directories marked with a CACHEDIR.TAG file. Add --exclude-file=/etc/restic/excludes for your own patterns. The first run uploads everything; later runs upload only changed data.

Do not rely on copying the live data directory of MySQL, MariaDB or PostgreSQL. Dump the databases to a file first (for example into /var/backups) and let restic back up the dump. Watch the exit code too: restic returns 0 on success, 1 on failure and 3 when the backup could not read some source data, which means the snapshot is incomplete.

Keep a sensible history: forget and prune

Removing old snapshots is a two-step job in restic: forget removes snapshots according to a policy, and prune deletes the data that only those snapshots referenced. The --prune option runs both in one go. Preview the policy with --dry-run first:

restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --dry-run
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune

The policy is applied per host and path group. Pruning locks the repository, so backups cannot complete while it runs; schedule it where it will not collide with your backup window. The documentation recommends running restic check after pruning.

Check that the data is really there

A plain restic check verifies the structure of the repository. It does not download and verify every data file unless you ask it to, because that means reading the whole repository back. A practical compromise is to check one slice of the data each week and cycle through all slices:

restic check
restic check --read-data-subset=1/5
# next week 2/5, then 3/5 ... 5/5

Restore, and practise restoring

List snapshots and files, then restore into an empty directory rather than over the live system:

restic snapshots
restic ls latest /etc/nginx
restic restore latest --target /tmp/restore-test --include /etc/nginx
restic restore latest --target /tmp/restore-test --dry-run --verbose=2

The --delete option of restore removes files in the target that are not in the snapshot. Use it only after a --dry-run, and never point it at a live directory casually.

Automate it with a systemd timer

A oneshot service can run several ExecStart= lines in order; if one fails, the rest are skipped and the unit is marked failed, which is what you want here. Create /etc/systemd/system/restic-backup.service (adjust the restic path to what command -v restic prints):

[Unit]
Description=restic backup to object storage
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
Nice=10
IOSchedulingClass=idle
ExecStart=/usr/bin/restic backup --one-file-system --exclude-caches /etc /home /var/www /var/backups
ExecStart=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune

And /etc/systemd/system/restic-backup.timer:

[Unit]
Description=Daily restic backup

[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=30min
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true catches up on a run that was missed while the server was off. Enable the timer, trigger one run by hand and read the log:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer
sudo systemctl start restic-backup.service
journalctl -u restic-backup.service -n 50

Hook the failed state into whatever alerting you already use; a backup job that fails silently for a month is the most common way these setups let people down.

Pitfalls to avoid

  • One copy of the password. If it exists only on the server and the server is lost, so are the backups.
  • Credentials that can delete everything. The key on the server can also delete the repository, so an attacker who controls the server can too. Where your provider supports it, bucket versioning or Object Lock, or a second copy made by a different machine, limits that risk.
  • Backing up live database files instead of dumps.
  • Never restoring. A repository that passes check is a good sign, but only a real restore proves you can get your data and configuration back.

What you should do

  1. Create a dedicated bucket and a key limited to it, and keep the restic password in two safe places.
  2. Set up the repository, run a first backup with database dumps included, and confirm it with restic snapshots.
  3. Add a retention policy with forget --prune, tested first with --dry-run.
  4. Schedule backups with a systemd timer and alert on failures.
  5. Run restic check regularly and restore a sample of files to a scratch directory at least once a month.

Sources

SKYLINE Engineering

@skyline

The engineering team at SKYLINE Industrial Solutions. We publish field-tested guides drawn from real KSA and GCC deployments.

See author profile
SKYLINE engineering services

Need this implemented for you?

Reading is free — building it right takes a team. SKYLINE engineers ship Backup & Disaster Recovery for Aramco vendors, banks, hospitals and government agencies across Saudi Arabia. Talk to us before you start.

Aramco Approved Contractor ISO 9001 · ISO 27001 SAMA CSF aligned NCA ECC ready 247+ KSA clients

Comments

0 total · 0 threads
Be the first to leave a comment.