# How to back up a Linux server with restic to S3 storage

> Encrypted nightly backups to any S3-compatible bucket with restic: database dumps, a systemd timer, a retention policy and a tested restore.

Source: https://vpsnine.com/tutorials/restic-backup-s3 · Updated: 2026-10-21

Difficulty: intermediate · Time: about 30 minutes · Tested on: Ubuntu 24.04 LTS

Prerequisites:

- A server running Ubuntu 24.04 or Debian
- Root or a user with sudo
- A bucket at any S3-compatible storage provider with an access key for it

To back up a Linux server with restic to S3, install restic, put the bucket's endpoint, access key and a repository password in a root-only environment file, run `restic init` once, and then have a systemd timer run `restic backup` every night followed by `restic forget --prune` to keep a sensible number of snapshots. restic encrypts everything on the server before it uploads and only sends data it hasn't stored before, so nightly runs are small and the storage provider never sees your files.

Any provider that speaks the S3 API works the same way; only the endpoint URL changes. Pick one in a different place from your server, so one incident can't take both.

The examples use these names. Replace them with yours:

| What | In this tutorial |
| --- | --- |
| The S3 endpoint | `https://s3.example.com` (your provider's S3 endpoint) |
| The bucket | `vps-backups` |
| The folder for this server in the bucket | `web1` |

## Install restic

```bash
sudo apt update
sudo apt install -y restic
restic version
```

Ubuntu 24.04 installs restic 0.16.4, Debian 12 has 0.14.0 and Debian 13 0.18.0. All of them do everything in this tutorial. The distribution builds leave out `restic self-update` (`unknown command "self-update"`); if you want the newest release, download the binary from [restic's GitHub releases](https://github.com/restic/restic/releases) instead and verify its checksum.

## Make the bucket and a key

At your storage provider:

1. Create a private bucket, `vps-backups`.
2. Create an access key that can only use that bucket (read, write, list and delete objects). Don't use an account-wide key: if the server is broken into, the key on it is the attacker's too.
3. Note the S3 endpoint URL and, if the provider asks for one, the region.

## Store the credentials on the server

Everything restic needs goes in two files only root can read. Create the directory and a strong repository password:

```bash
sudo mkdir -p /etc/restic
sudo chmod 700 /etc/restic
openssl rand -base64 32 | sudo tee /etc/restic/password > /dev/null
sudo chmod 600 /etc/restic/password
```

Now `/etc/restic/env`:

```ini
RESTIC_REPOSITORY=s3:https://s3.example.com/vps-backups/web1
RESTIC_PASSWORD_FILE=/etc/restic/password
AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY
```

```bash
sudo chmod 600 /etc/restic/env
```

If your provider needs a region, add `AWS_DEFAULT_REGION=...`. The `AWS_` names are just what the S3 protocol's clients read; they work with every provider.

**Copy the repository password somewhere off the server now**, into your password manager. The backups are encrypted with it. If the server is lost, so is the copy in `/etc/restic`, and without the password nobody, including your provider, can read the backups.

## Create the repository

Work as root for the rest of the tutorial and load the environment file into your shell:

```bash
sudo -i
set -a; . /etc/restic/env; set +a
restic init
```

```text
created restic repository e6ad518975 at s3:https://s3.example.com/vps-backups/web1

Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
```

Run `set -a; . /etc/restic/env; set +a` again in any new root shell before using restic by hand.

## Choose what to back up

You don't need the whole disk: the operating system comes back from a reinstall. Back up what you made. List the paths in `/etc/restic/include.txt`:

```text
/etc
/home
/root
/var/www
/var/backups/db
```

Add anything else your apps keep, such as `/srv`, `/opt/yourapp` or Docker volumes under `/var/lib/docker/volumes`. Then the things not worth storing, in `/etc/restic/exclude.txt`:

```text
/root/.cache
/home/*/.cache
**/node_modules
```

Don't list a live database's data directory (`/var/lib/postgresql`, `/var/lib/mysql`). Files copied while the database writes to them don't make a consistent copy. Dump the databases first; the next script does.

## Write the backup script

Create `/usr/local/sbin/restic-backup`:

```bash
#!/bin/bash
# /usr/local/sbin/restic-backup: dump the databases, back up, apply the retention policy.
set -euo pipefail

# 1. Database dumps (keep the lines for the databases you run).
mkdir -p /var/backups/db
chmod 700 /var/backups/db
if command -v pg_dump >/dev/null; then
  sudo -u postgres pg_dump -Fc appdb > /var/backups/db/appdb.pgdump
fi
if command -v mariadb-dump >/dev/null; then
  mariadb-dump --single-transaction --routines --events --all-databases | gzip > /var/backups/db/mariadb.sql.gz
fi

# 2. The backup itself.
restic backup \
  --files-from /etc/restic/include.txt \
  --exclude-file /etc/restic/exclude.txt \
  --exclude-caches \
  --tag nightly

# 3. Keep 7 daily, 4 weekly and 6 monthly snapshots; delete the data nothing else uses.
restic forget --tag nightly --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

# 4. Check the repository's structure, and read back a different tenth of the data each day.
restic check --read-data-subset="$(( $(date +%-d) % 10 + 1 ))/10"
```

```bash
chmod 700 /usr/local/sbin/restic-backup
```

What it does:

- **Database dumps.** `pg_dump` and `mariadb-dump` write consistent copies to `/var/backups/db`, which is in the include list. Change `appdb` to your database (or add a line per database). As root on Ubuntu and Debian, `mariadb-dump` logs in through the Unix socket without a password. Setting up the databases themselves is in [install PostgreSQL](https://vpsnine.com/tutorials/install-postgresql-ubuntu) and [install MariaDB](https://vpsnine.com/tutorials/install-mariadb-ubuntu).
- **`restic backup`.** `--exclude-caches` also skips any directory marked with a standard `CACHEDIR.TAG` file. The `nightly` tag keeps these snapshots apart from any you take by hand.
- **`restic forget --prune`.** Applies the retention policy and deletes data no remaining snapshot uses. Change the numbers to taste; `restic forget --help` lists the other `--keep-*` options.
- **`restic check`.** Verifies the repository's structure every night and downloads a tenth of the stored data to check it, a different tenth each day, so over a month everything is read back at least once.

### Check it worked

Run it once by hand (the environment is still loaded in your root shell):

```bash
/usr/local/sbin/restic-backup
```

The output ends with the snapshot it saved, the policy applied and `no errors were found`. List the snapshots:

```bash
restic snapshots
```

```text
ID        Time                 Host        Tags        Paths
------------------------------------------------------------------------
9bd2dd52  2026-10-21 23:47:17  web1        nightly     /etc
                                                       /home
                                                       /root
                                                       /var/backups/db
                                                       /var/www
------------------------------------------------------------------------
1 snapshots
```

The first run uploads everything; later runs only send what changed.

## Run it every night with a systemd timer

A systemd timer beats cron here: it logs to the journal, catches up on a run missed while the server was off, and won't start a second copy while one is still running.

Create `/etc/systemd/system/restic-backup.service`:

```ini
[Unit]
Description=Back up this server with restic
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=/usr/local/sbin/restic-backup
Nice=10
IOSchedulingClass=idle
```

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

```ini
[Unit]
Description=Run the restic backup every night

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target
```

`OnCalendar` is in the server's time zone (UTC on most servers). `RandomizedDelaySec` spreads the start over half an hour, and `Persistent=true` runs a missed backup at the next boot. `Nice` and `IOSchedulingClass=idle` keep the backup from slowing your apps down.

Turn the timer on and start one run now, to see it work under systemd:

```bash
systemctl daemon-reload
systemctl enable --now restic-backup.timer
systemctl start restic-backup.service
```

### Check it worked

```bash
systemctl list-timers restic-backup.timer
journalctl -u restic-backup.service -n 30 --no-pager
```

The first shows when the next run is due. The second shows the last run's output, ending with `no errors were found`. `systemctl status restic-backup.service` shows `status=0/SUCCESS` after a good run; a failed run leaves the unit in the `failed` state, which [monitoring](https://vpsnine.com/help/monitor-your-server) can alert on.

## Restore a file, and prove you can

A backup you haven't restored from is a guess. Try it now, and again every few months.

See what is in the newest snapshot:

```bash
restic ls latest /var/www
```

Restore one directory into a scratch location (not over the live copy):

```bash
restic restore latest --target /tmp/restore --include /var/www/site
ls -la /tmp/restore/var/www/site
```

Print one file straight to the terminal or a pipe, for example a database dump:

```bash
restic dump latest /var/backups/db/mariadb.sql.gz | gunzip | head
```

For a whole-server disaster, the steps are: build a new server, install restic, recreate `/etc/restic/env` and the password from your password manager, run `restic restore latest --target /`, then restore the databases from their dumps (`pg_restore` or `mariadb`). Practise it once on a spare server; it is the only way to know nothing is missing.

## Protect the backups from the server

The access key on the server can delete objects, and `restic forget --prune` needs that. Someone who breaks into the server can therefore delete your backups too. Two ways to close that gap:

- Turn on **object versioning** or **object lock** on the bucket, if your provider offers it, with a retention period longer than the time you'd need to notice a break-in. Deleted or overwritten objects stay recoverable.
- Or prune from somewhere else: give the server a key that can't delete, and run `restic forget --prune` from another machine with a full-rights key.

[Server snapshots](https://vpsnine.com/help/snapshots) are a useful extra, but they live with the server's provider; restic's copy is the one that survives losing the account or the server.

## Troubleshooting

**`Fatal: wrong password or no key found`.** The password in `/etc/restic/password` isn't the one the repository was created with, or `RESTIC_PASSWORD_FILE` isn't set because the environment file wasn't loaded. Run `set -a; . /etc/restic/env; set +a` and try again.

**`Fatal: unable to open config file: Stat: The request signature we calculated does not match the signature you provided. Check your key and signing method.`** The secret key is wrong. Copy it again from your provider, without spaces or quotes.

**`Fatal: unable to open config file: Stat: The specified key does not exist.`** followed by `Is there a repository at the following location?`. The repository hasn't been created at that address: check the bucket and folder in `RESTIC_REPOSITORY`, or run `restic init`.

**`Fatal: create key in repository at ... failed: repository master key and config already initialized`.** You ran `restic init` against a repository that already exists. Nothing is wrong; leave it.

**An error saying the repository `is already locked`.** Another restic run holds the lock, or a previous run was interrupted and left it behind. Make sure no restic is running (`pgrep restic`), then `restic unlock`.

## Next steps

- Prefer backing up to a second server over SSH? See [borgmatic with BorgBackup](https://vpsnine.com/tutorials/borgmatic-backup-setup).
- The basics of what to back up and why are in [backups you should run](https://vpsnine.com/help/backups-you-should-run).
- The [restic documentation](https://restic.readthedocs.io/) covers `restic mount` (browse snapshots as a folder), other backends and tuning.
