Backups are the discipline where most is learned the hard way and least written about until it hurts. After several incidents where the backup existed but wasn’t restorable, others where the destination was the same server that burned, and one late discovery that encryption wasn’t on, I’ve ended up with a restic-based routine that covers the basics without pretending to be aerospace engineering. This post documents how to install it on Debian, set up an encrypted repository on S3-compatible storage, automate daily backups with systemd, and crucially, verify that restore works before you need it.
Why restic
Options in the open mature backup space are several: restic, borg, duplicity, rclone with encryption, and more recently kopia. All solve the hard parts (encryption, deduplication, incrementals), but with different philosophies. The reason I use restic in most cases is a combination of three properties: it writes to any S3-compatible repository without extra layers, it has a simple verifiable encryption model, and its repository format has been stable for years with high community trust. Borg is excellent but requires a service listening on the destination; duplicity is older and works with chained patch files that complicate partial restores; kopia is modern but still rotating pieces.
restic encrypts everything client-side with AES-256 before sending to the repository. The storage operator sees nothing but sizes and opaque metadata. It deduplicates at the variable-block level, so a modified file only transmits the changed blocks, and stores the result as manageable-sized objects. A restic repository has all the data needed to rebuild itself, with no server at the remote end holding state.
Installing on Debian 13
Installing on Debian is straightforward. The official repository package is usually a couple of versions behind, so for production I prefer installing from the project’s official binaries, which ship signed and are easy to verify. In 2025 the stable version is 0.18.
Alternatively you can use apt install restic and accept the repository version, which works for most cases. The practical difference is bug fixes and some performance improvements in recent releases.
Setting up a repository on S3-compatible storage
restic works with many repository types: local directory, SFTP, S3, Backblaze B2, Azure, Google Cloud Storage. The case I cover here is a repository on an S3-compatible service, which is the pattern that best separates backup data from the protected server. Hetzner Object Storage works well, as do Backblaze B2, Wasabi, or an AWS bucket.
The first step is to generate credentials with minimum permissions: only object creation, listing, and reading in the target bucket. Delete permission isn’t required because restic manages its own lifecycle and deletions can be done with a separate credential to limit blast radius.
The password stored in /etc/restic-password is the only thing needed to decrypt the repository. Keeping it additionally in a secondary location off the server is a fundamental part of the strategy: lose the password and you lose the whole repository even if the data is still in the bucket. A password manager, a physical safe with the printed value, another server replicating it, three hosts as I do in my infrastructure. The specific option matters less than having at least two independent copies.
First backup
Once the repository is initialized, the first backup is a simple command. What goes into the exclude file deserves care. There are directories you shouldn’t include: /tmp, anywhere with large caches or Unix sockets, the container manager’s own data paths if you already cover their volumes, and unencrypted sensitive files that shouldn’t travel to the repository even if restic encrypts them. Reviewing this file periodically prevents backups that grow for no reason.
The first backup takes time because it transmits everything. Later ones are much faster because restic deduplicates against what’s already there: only new blocks travel. In my experience an incremental over a server with tens of gigabytes of data takes between thirty seconds and two minutes, dominated by file enumeration rather than network.
Automation with systemd
The pattern I use is a timer plus service pair, because it gives better observability and control than cron. The service runs a script that performs the backup, applies retention policy and emits Prometheus metrics via the textfile collector. The timer fires daily.
The script performs the backup, applies forget with the retention policy (keep the last seven daily, four weekly, twelve monthly, for example), runs prune to free space, and writes a metrics file in /var/lib/node-exporter/textfile/restic.prom with success, duration and size. Those metrics are then watched by alert rules warning if the backup fails or hasn’t run in more than 36 hours. The second alert type, metric absence, catches silent failures where the script doesn’t even get to write a result.
Restore verification
The part most often forgotten and the one that hurts most when needed. A repository you’ve never run restore on isn’t a backup, it’s an illusion. The routine I keep is weekly: a separate timer fires a script that picks a recent snapshot, extracts a known subset to a temp directory, compares against a reference snapshot, and reports the result. If verification fails, I get an immediate alert.
Besides the sample restore, once a week I run restic check --read-data-subset=5% which verifies integrity of a fraction of the repository by reading and comparing against stored hashes. In six months I’ve caught two faults: one was an expired credential the script wasn’t reporting as error, another was a corrupted block the storage provider reprovisioned after a ticket. Without weekly verification neither would have been caught until disaster.
How to think the decision
My reading after several years running restic is that it solves the technical part of backups elegantly and lets the operator focus on the hard part, the operational one: where the password lives, who has access, what if the storage provider vanishes, how often you verify restore, how many days of history to keep. No tool solves that, and an encrypted repository without a recovery plan is barely less fragile than having no backup.
The reasonable minimum proposal for a production server is this: restic installed from verified binaries, repository on an S3 provider independent from the server, password replicated off-server in at least two places, daily backup via systemd with retention policy, weekly sample restore, and alerts if any of this fails or stops reporting metrics. All of that takes an afternoon the first time, fifteen minutes from the second, and prevents the class of disaster that ends careers. Time spent cold is trivial compared to time saved when something goes wrong, and hopefully you won’t need it, but when you do you’ll be glad you set it up before.