Backups

resticprofile examples: a production configuration, explained

The official documentation describes every key resticprofile accepts, one reference page at a time. It does not show how they fit together on a server you depend on, or where retention belongs when that server is not allowed to delete its own backups.

The short answer

A production resticprofile setup needs two configuration files. The server's profile holds the repository, an env-file with a storage key that cannot delete data, a password-file, the sources and excludes, a database dump in run-before and a systemd schedule. Because that key cannot delete backup data, forget, prune and check run from a second profile on an admin host, using keep-within rules and read-data-subset. Both samples below are YAML, checked against resticprofile 0.33.1.

Versions and file format

We checked everything here against resticprofile 0.33.1, the latest release, published on 15 April 2026, and restic 0.19.1, published on 5 July 2026. resticprofile reads TOML, YAML, JSON and HCL, and its documentation recommends TOML or YAML. The samples are YAML, and both produce the expected restic command lines under that version's show and --dry-run.

Both files declare version: "1". Configuration format 2 exists, but the current documentation still calls it a preview that is "for testing and feedback only", so a production file should not depend on it. In version 1 a retention section does nothing until you switch it on, and some inherited lists merge into the parent's list by position instead of replacing it. Both behaviours come up below.

Two hosts, two profiles

The server backs up with a storage key that cannot delete data, so whoever compromises the server cannot destroy its history. restic's documentation spells out the consequence: forget and prune "require full read, write and delete access to the repository", and the recommended setup for append-only repositories is a separate, well-secured client for that maintenance. So the server profile has no retention at all. A second profile, on an admin host the server cannot reach, removes old snapshots, prunes and checks the data. The Backblaze B2 version cleanup belongs there too.

The admin host also holds a copy of the repository password. restic warns that losing the password means the data is irrecoverably lost, and a password that only exists on the server disappears with the server.

The server profile

# /etc/resticprofile/profiles.yaml on the server (web1)
version: "1"

web1:
  repository: "s3:https://objects.example.net/acme-web1-restic"
  password-file: "/etc/resticprofile/web1.key"
  env-file:
    - "/etc/resticprofile/web1.env"
  lock: "/run/resticprofile-web1.lock"
  status-file: "/var/lib/resticprofile/status.json"
  prometheus-save-to-file: "/var/lib/node_exporter/textfile/resticprofile.prom"

  backup:
    source:
      - "/etc"
      - "/home"
      - "/srv"
      - "/var/www"
      - "/var/backups/db"
    exclude:
      - "node_modules"
      - "/home/*/.cache"
    exclude-file: "excludes.txt"
    exclude-caches: true
    tag:
      - "web1"
    extended-status: true

    run-before: "runuser -u postgres -- pg_dump --format=custom shop > /var/backups/db/shop.dump"
    run-after-fail: 'logger -t resticprofile "web1 backup failed: $ERROR_MESSAGE"'
    run-finally: "rm -f /var/backups/db/shop.dump"

    schedule: "*-*-* 01:30"
    schedule-permission: system
    schedule-lock-wait: 2h
    schedule-log: "/var/log/resticprofile-web1.log"
    schedule-after-network-online: true
# /etc/resticprofile/web1.env   (owner root, mode 600)
# Storage key that can delete only restic's lock files. The key that can delete data stays on the admin host.
AWS_ACCESS_KEY_ID=server-key-id
AWS_SECRET_ACCESS_KEY=server-key-secret
# AWS_DEFAULT_REGION=eu-central-1   only if your provider needs a region
# on web1, as root, once
install -d -m 700 /etc/resticprofile /var/backups/db
install -d -m 755 /var/lib/resticprofile
install -d -m 755 /var/lib/node_exporter/textfile   # or your textfile collector's directory
resticprofile generate --random-key > /etc/resticprofile/web1.key
# write web1.env as shown above, then
chmod 600 /etc/resticprofile/web1.key /etc/resticprofile/web1.env
# copy web1.key to the admin host before anything else

Repository and secrets

repository uses restic's form for S3-compatible storage, s3:https://host/bucket. restic reads the storage credentials from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, which live in the dotenv file named by env-file. The repository password lives in password-file. Both files are root-only, so the YAML holds no secret and can go into version control.

The server key needs one narrow delete right. restic removes its lock file at the end of every command, and with a key that cannot delete anything each run leaves its lock behind. The backup still exits 0, but the admin host's check and forget then fail with "repository is already locked". Allow deletes under locks/ only, which is also what restic's rest-server permits in append-only mode. On MinIO, this policy left no locks behind and still refused to delete snapshots:

{
  "Version": "2012-10-17",
  "Statement": [
    {"Effect": "Allow", "Action": ["s3:GetBucketLocation", "s3:ListBucket"], "Resource": ["arn:aws:s3:::acme-web1-restic"]},
    {"Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"], "Resource": ["arn:aws:s3:::acme-web1-restic/*"]},
    {"Effect": "Allow", "Action": ["s3:DeleteObject"], "Resource": ["arn:aws:s3:::acme-web1-restic/locks/*"]}
  ]
}

One default works against the secrets split. schedule-capture-environment defaults to RESTIC_*, so every RESTIC_ variable present when you run resticprofile schedule is copied into the generated systemd unit as an Environment= line, and resticprofile 0.33.1 writes unit files with mode 0644. An exported RESTIC_PASSWORD in that shell ends up readable by every local account, and systemctl show prints it to them even if you tighten the file mode. Run schedule from a clean shell, or set schedule-capture-environment: [] in the section that holds the schedule.

Source, excludes and tags

An exclude pattern without a leading slash matches a complete path component anywhere, so node_modules drops every directory of that name. A leading slash anchors the pattern at the root. exclude-file takes one pattern per line with # comments, and a relative path resolves against the directory holding the configuration file. A path listed directly in source is always saved, but excludes still filter what is inside it: list /srv/app/node_modules as a source while node_modules is excluded and the snapshot holds only the empty directory. exclude-caches skips the contents of any directory marked with a CACHEDIR.TAG file and keeps the tag file itself.

tag labels every snapshot, and the admin side makes use of it. Running resticprofile web1.backup --tag keep before a risky upgrade adds a keep tag, and the maintenance policy's keep-tag never removes that snapshot.

The database dump

The dump sits in the backup section's run-before. At the profile root it would run before every restic command the profile runs, snapshots included. pg_dump makes a consistent export even while the database is in use.

A hook that exits non-zero aborts the run and triggers run-after-fail, which receives ERROR_MESSAGE, ERROR_EXIT_CODE and ERROR_STDERR. In my view that is the right default, because a snapshot without its database is a failure you only discover during a restore. Cleanup belongs in run-finally rather than run-after, because run-after only runs when everything succeeded, and a failed night would otherwise leave an unencrypted dump on disk until the next good run.

Scheduling with systemd

schedule takes systemd calendar syntax, so systemd-analyze calendar "*-*-* 01:30" shows the next run. With schedule-permission: system, resticprofile writes resticprofile-backup@profile-web1.service and a matching timer into /etc/systemd/system, and the job runs as root. The default timer sets Persistent=true, so a run missed while the machine was off starts once the timer is active again after boot. To change the permission later, unschedule first, edit, then schedule again.

lock is resticprofile's own lock file, which stops two runs of the profile overlapping. schedule-lock-wait is how long a scheduled run waits for that lock and for restic's repository lock. It matters here because in restic 0.19.1 check, forget and prune all take an exclusive lock, and the admin host runs them against the same repository. schedule-log sends output to a file that resticprofile appends to but never rotates, so give it a logrotate rule, or leave the key out and systemd puts the output in the journal instead. schedule-after-network-online adds After=network-online.target. That only sets the order. The unit gets no Wants= line, so it waits for the network only if something else on the host pulls that target in.

The unit calls resticprofile run-schedule, which reads the configuration afresh every time. Changes to sources, excludes or hooks apply on the next run. Changing when a job runs, or as whom, needs resticprofile schedule again.

Status and metrics

status-file keeps a JSON summary of the last backup, forget and check: success, time, error and duration. resticprofile stores the forget result under the key retention, so look for that name in your monitoring. prometheus-save-to-file writes metrics in Prometheus text format, for the backup command only; point it at your textfile collector's directory, and keep the .prom ending node_exporter expects. extended-status: true adds file and byte counts at the cost of restic's normal progress output. resticprofile creates neither file's directory, which is why the setup above creates both.

Retention versus forget

Both end in restic forget. The difference is when it runs and what it selects.

Aspectretention sectionforget section
When it runsInside resticprofile backup, before or after the backupWhen you run resticprofile forget, or on its own schedule
Switched on byafter-backup: true or before-backup: true. In version 1 neither is the default, so keep rules alone do nothingAlways active when called
After a failed backupSkipped, because after-backup only follows a successful backupIndependent of backups
Which snapshotsAdds the backup's source list as --path unless you set pathExactly the flags you write
SchedulingDeprecated. Schedule forget insteadSupported
Delete rights needed onThe server that runs the backupWhichever host runs it

The path default is the subtle one. restic only considers snapshots that include every listed path, so after you add a directory to source, retention stops matching the snapshots taken before the change and never removes them. The last row is why retention cannot live on an append-only server.

The admin maintenance profile

# /etc/resticprofile/profiles.yaml on the admin host
version: "1"

# Shared rules only: no repository, no schedule, never run directly.
policy:
  forget:
    keep-within: "14d"
    keep-within-weekly: "3m"
    keep-within-monthly: "1y"
    keep-tag:
      - "keep"
    prune: true
  check:
    read-data-subset: "{{ .Now.Day }}/31"

web1:
  inherit: policy
  repository: "s3:https://objects.example.net/acme-web1-restic"
  password-file: "/etc/resticprofile/keys/web1.key"
  env-file:
    - "/etc/resticprofile/web1-admin.env"   # the key that CAN delete
  status-file: "/var/lib/resticprofile/web1.json"
  forget:
    schedule: "Sun 03:00"
    schedule-permission: system
    schedule-lock-wait: 2h
    schedule-log: "/var/log/resticprofile-web1-forget.log"
  check:
    schedule: "*-*-* 05:30"
    schedule-permission: system
    schedule-lock-wait: 2h
    schedule-log: "/var/log/resticprofile-web1-check.log"

The policy keeps every snapshot from the last 14 days, the newest of each week for three months and the newest of each month for a year. In restic durations, m means months. The plain keep-within is deliberate. restic's documentation warns that an attacker with append-only access can add garbage snapshots so that a count-based rule, such as keeping three weekly snapshots, keeps the fakes and forgets the real ones. It recommends keep-within for this setup, because that rule keeps every snapshot in the window, real ones included. The bucketed keep-within-daily, -weekly and -monthly forms keep only the newest snapshot in each day, week or month, so a planted snapshot a few minutes newer than each real one displaces it just as with a count. Beyond 14 days the weekly and monthly rules carry that risk, which is why the admin host should look at unexpected snapshots before the window runs out. restic measures the windows from the newest snapshot and ignores snapshots dated in the future. prune: true adds --prune, so restic prunes only when forget removed something.

read-data-subset uses the .Now.Day template, so the check on the 23rd downloads and verifies group 23 of 31, about a thirty-first of the pack files. A 31-day month reads every group once; shorter months skip the highest groups until the next long one. A percentage such as 5% is simpler, but restic notes that random subsets do not guarantee full coverage. Either way, reading data costs download traffic from the storage provider.

The policy profile holds only the rules and each server's profile inherits it. Keep list values in one place and check the result with show. In version 1 under 0.33.1, source and exclude merge into the parent's list by index, while tag and keep-tag replace the parent's list outright. A one-item child source therefore replaces only the parent's first entry.

Commands to run before trusting it

# on the admin host, once, before the first backup
resticprofile web1.init

# on the server
resticprofile web1.show               # the profile as resticprofile resolved it
resticprofile --dry-run web1.backup   # print hooks and restic command, run nothing
resticprofile web1.backup             # one real run, end to end
resticprofile schedule --all          # install every schedule in the file
resticprofile status --all            # the installed systemd timers
systemctl list-timers 'resticprofile-*'

# on the admin host
resticprofile web1.forget --dry-run   # restic lists what the policy would remove
resticprofile web1.check              # today's slice of the data, by hand
resticprofile schedule --all
  1. Compare show with the dry-runshow prints the resolved profile with inheritance applied. --dry-run before web1.backup prints every hook and the restic command line and runs nothing. Compare the two. restic-arguments-filter is on by default and drops flags restic does not recognise, so a mistyped keep-within-daly appears in one and vanishes from the other.
  2. Let restic judge the policyFlags after the command go to restic, so web1.forget --dry-run on the admin host makes restic list what it would keep and remove, and remove nothing.
  3. Schedule everything as the same userRun schedule --all as root. The documentation warns against mixing user and system schedules in one run.
  4. Know which status you are readingresticprofile status reports the installed timers and journal lines at warning level or above from the last month. With schedule-log set, a failed night shows there only as systemd's "Failed with result 'exit-code'" line, without the reason. The status file and the log hold the result.
  5. Restore somethingA clean check proves the repository is intact. It does not prove that the repository holds what you need. Restore to a scratch machine and time it.

On Windows

The same keys work. resticprofile schedules through Task Scheduler on Windows 10 or later. A system schedule needs elevation to install, which resticprofile requests itself, and user asks for the account password. schedule-start-when-available, added in 0.33.0, runs a backup that was missed while the machine was off, and use-fs-snapshot: true in the backup section lets restic read locked files through VSS. If restic.exe is not detected, set its full path as restic-binary under global. The two-profile split carries over unchanged. If you are still choosing a tool, read restic versus Borg first.

Downsides of the append-only split, and how Veeam compares

The split protects history by withholding one permission from the server. That is access control. It does not make the backups immutable, and its costs are worth weighing against a product built around immutability, such as Veeam Backup & Replication 13.

Veeam answers the first and third points differently. Its hardened repository is a Linux server on which backup files "cannot be moved, modified or deleted, but can be copied" for an immutability period of 7 to 9,999 days. Veeam uses the credentials that deploy it once and does not store them, so a compromised backup server cannot reach it. On object storage, Veeam sets S3 Object Lock in compliance mode on every object it uploads and manages the lock per backup chain. restic cannot do that. It runs on a locked bucket but never sets or extends a lock itself, as the Object Lock guide shows.

Veeam's version has its own costs:

In our view the restic split suits one to a handful of Linux servers on object storage. It is small, cheap and scriptable, and its weak points are known and testable. If you need backups that nobody, including you, can delete for a fixed period, or you already run VMware or Hyper-V, Veeam's hardened or object-lock repository gives you that and this design does not. Between the two sits restic on a bucket with Object Lock, with the limits described in that guide.

Gotchas

How this was tested. On 23 September 2026 we built resticprofile from the v0.33.1 tag with Go 1.26.8 and ran it with restic 0.19.1 against MinIO RELEASE.2025-09-07T16-13-09Z in Docker on an Ubuntu 24.04 host, as an S3-compatible stand-in, with one key lacking s3:DeleteObject and one with full rights. The server and admin host were two Debian 13 containers running systemd 257 as PID 1, with PostgreSQL 17.11 for the dump hook. We ran both samples as printed apart from endpoint and credentials, started the units by hand, and checked Persistent= by stopping and restarting a container rather than with a real reboot. Not tested: a commercial S3 provider, Windows and Task Scheduler, node_exporter reading the .prom file, and the check rotation over a whole month. The Windows details come from the documentation, and we rendered the check template for day 23 only. The downsides section rests on these tests and the linked guides. The Veeam comparison comes from Veeam's version 13 documentation and was not tested, except that on 26 September 2026 we cleared the immutable attribute as root with chattr -i, using e2fsprogs 1.47.0 on ext4, and then deleted the file.

Sources

  1. resticprofile releases (v0.33.1, 15 April 2026), creativeprojects on GitHub
  2. Getting started: file formats, resticprofile documentation
  3. Configuration v2 proposal, resticprofile documentation
  4. Profile reference and retention section reference, resticprofile documentation
  5. Schedule configuration reference (capture-environment) and global section reference (restic-arguments-filter, restic-binary), resticprofile documentation
  6. Schedules: retention scheduling deprecated, resticprofile documentation
  7. Schedule configuration, systemd and schedule commands, resticprofile documentation
  8. Command hooks, variables and inheritance, resticprofile documentation
  9. Status file and Prometheus, resticprofile documentation
  10. Windows Task Scheduler, resticprofile documentation
  11. systemd/generate.go at v0.33.1 (unit file mode), resticprofile source
  12. restic releases (0.19.1, 5 July 2026), restic on GitHub
  13. Removing backup snapshots, including security considerations in append-only mode, restic documentation
  14. Working with repositories: checking integrity, restic documentation
  15. Backing up: excluding files and VSS, restic documentation
  16. Preparing a new repository: S3-compatible storage, restic documentation
  17. cmd_check.go at v0.19.1 (exclusive lock), restic source
  18. repo/repo.go at v0.14.0 (append-only mode still allows deleting locks), rest-server source
  19. pg_dump, PostgreSQL 18 documentation
  20. systemd.timer(5): Persistent= and systemd.exec(5): StandardOutput=, man7.org
  21. WriteToTextfile, Prometheus Go client documentation
  22. Hardened Repository, Requirements and Limitations and How Immutability Works, Veeam Backup & Replication 13 User Guide
  23. How Immutability Works (object storage, block generation) and Considerations and Limitations, Veeam Backup & Replication 13 User Guide
  24. Licensing, Veeam Backup & Replication 13 User Guide, and Downloads (Community Edition, up to 10 workloads), Veeam
  25. chattr(1): the immutable attribute, man7.org

Questions

What is the difference between retention and forget in resticprofile?

Both run restic forget. The retention section runs it inside the backup command, before or after the backup, and only when before-backup or after-backup is set; in config version 1 neither is on by default. It also passes the backup source list as --path unless you override it. The forget section configures the standalone forget command and can carry its own schedule. Scheduling retention directly is deprecated in favour of forget.

How do I schedule resticprofile check?

Add schedule, schedule-permission and schedule-lock-wait to the check section of the profile, then run resticprofile schedule. Put read-data-subset in the same section so each run downloads and verifies part of the pack files rather than only the repository structure. In restic 0.19.1 check takes an exclusive lock, so run it away from the backup window and give the backup a schedule-lock-wait long enough to outlast it.

How does resticprofile schedule backups on Linux?

With systemd available, resticprofile schedule writes a service and a timer named resticprofile-backup@profile-NAME and enables them. The schedule key uses systemd calendar syntax, so systemd-analyze calendar can test it. With schedule-permission: system the units go into /etc/systemd/system and run as root. The service calls resticprofile run-schedule, which re-reads the configuration, so only a change to when or as whom a job runs needs resticprofile schedule again.

Does resticprofile work on Windows?

Yes. It schedules through Windows Task Scheduler on Windows 10 or later and uses the same configuration keys. Installing a schedule with schedule-permission: system needs an elevated prompt, and user asks for the account password. Since version 0.33.0, schedule-start-when-available runs a backup that was missed while the machine was off. Add use-fs-snapshot: true to the backup section so restic reads locked files through VSS.

How do exclude patterns work in resticprofile?

resticprofile hands exclude and exclude-file to restic, so restic pattern rules apply. A pattern without a leading slash matches a complete path component anywhere, so node_modules drops every directory of that name, while a leading slash anchors the pattern at the root. A relative exclude-file path resolves against the directory holding the configuration file. A path listed directly in source is always saved, but excludes still filter what is inside it, so a source directory whose own name matches an exclude is saved empty.

Is an append-only restic key as safe as Veeam immutable backups?

No. An append-only key is access control. The backed-up server cannot delete its history, but whoever holds the admin key or the storage account can, and the server key can still overwrite objects, so recovery depends on bucket versioning. Veeam hardened repositories and Veeam object-lock repositories make backup files undeletable for a set period, even to a compromised backup server. The restic split is cheaper and simpler, but it does not make backups immutable.

Want resticprofile run for you?

Every Wardio-managed server backs up with resticprofile to storage in a different failure domain, with scheduled integrity checks. See pricing.

Free server review