My new backup routine with rustic
A while ago I presented my backup routine here on the blog using the excellent restic. Now I bring my impressions, installation instructions and a hands-on benchmark of rustic, a Rust reimplementation fully compatible with restic repositories, faster, lock-free and packed with new features that are already running daily on my Manjaro desktop.
A while ago, I wrote the post "My backup routine with restic", where I presented what was, until then, my favorite tool for ensuring the integrity of my data and my peace of mind about it. Recently, though, I decided to put a new contender to the test — or rather, a very close and modern cousin — called rustic (GitHub repository and official documentation).
Let me say up front that I am testing rustic in practice and, so far, I am very happy with it! As I said when I wrote about Croc and Magic-Wormhole, about using Atuin for your shell history or in my update on how Atuin got even better (which is also written in Rust), it is always exciting to find a command-line utility that solves real everyday Linux problems with elegance and speed.
Who created rustic, and why?
To understand why rustic is so interesting, it is worth knowing the story behind it. The tool was created by Alexander Weiss (known on GitHub and on the restic forum as @aawsome). And he was no random adventurer parachuting into the backup world: before creating rustic, Alexander was already one of the main contributors to the restic project itself, having even received the Google Open Source Peer Bonus for the huge performance improvements in data pruning (prune) and in the v2 repository compression support he helped implement there.
As the author himself tells in the official thread where he introduced the project on the restic forum and in the documentation FAQ, what motivated him to create rustic was a combination of two factors:
- Wanting to master Rust in practice: he wanted to learn Rust in depth and concluded that the best way to understand the language's pros and cons was to reimplement from scratch a complex, performance-critical system he already knew inside out.
- restic's conservative pace: as a mature project (written in Go) heavily used in production,
resticis naturally conservative. Code reviews took a long time, and several advanced features the community had requested for years ran into the maintainers' caution.
Alexander's big insight was not to reinvent the wheel where restic was already perfect. Instead of creating a proprietary, incompatible repository format, he designed rustic to read and write exactly the same encrypted format specified by restic.
That is, rustic works as a drop-in replacement. And that was one of the things that got me most excited: it is 100% compatible with my existing restic repositories. I didn't have to recreate repositories from scratch, re-upload hundreds of gigabytes over the network, or give up my history of old snapshots. I just pointed rustic at where restic already kept the backups and everything worked right away.
How to install rustic
Since the project has gained a lot of traction, installing rustic on Linux (or even on macOS and Windows) is very simple, as detailed on the official installation page:
- On Arch Linux and Manjaro: the package is already available directly in the official
extra/rusticrepository, so just run:
sudo pacman -S rustic
- Prebuilt static binary (
musl) on any Linux distribution: if you are on a distribution that doesn't packagerusticyet (such as some Debian/Ubuntu versions) or are running a disaster-recovery Live USB, you can download the single static binary directly from the GitHub Releases:
curl -L https://github.com/rustic-rs/rustic/releases/latest/download/rustic-x86_64-unknown-linux-musl.tar.gz \ | tar -xz && sudo mv rustic /usr/local/bin/
- Via Cargo (Rust ecosystem): for those who already have
cargoinstalled (or use cargo-binstall):
cargo binstall rustic-rs # or building from source via crates.io: cargo install --locked rustic-rs
Speed, memory usage and my own benchmark
Keeping the four pillars we listed in the previous post about restic — an encrypted, deduplicated, versioned and compressed repository — where rustic really shines from the first contact is raw performance and resource savings.
Being written in Rust, without the overhead of Go's garbage collector and with much stricter memory management, both the reports in the original restic forum thread and in the discussions about slow prune, and the official rustic vs. restic comparison page, highlight two big advantages:
- Drastically lower RAM usage on the initial backup and when indexing millions of small files (a scenario where
resticoften consumes gigabytes of RAM and can hit Out-Of-Memory on smaller VPSs). - Much faster maintenance operations (
check,forgetandprune), thanks to batch reading of contiguous blobs and the two-phase pruning algorithm.
So as not to rely only on third-party numbers, I decided to run my own benchmark here on my desktop, comparing restic 0.19.1 (built with Go 1.26.4) and rustic 0.11.4 head to head.
So you know exactly where the numbers come from, these are the specs of the machine where the test ran:
- Processor (CPU): AMD Ryzen 5 8600G w/ Radeon 760M Graphics (6 cores / 12 threads)
- RAM: 32 GB DDR5 (30 GiB available to the system after the iGPU reservation)
- Storage: 1 TB Kingston NV3 NVMe SSD (
KINGSTON SNV3S1000G) with a Btrfs file system on the source partition and test repositories ontmpfs(RAM) to isolate the write bottleneck and measure purely the speed of reading, deduplication, compression and encryption - Operating System: Manjaro Linux 26.1.3 (Bian-May), Linux kernel
7.2.9-1-MANJARO(x86_64)
The dataset chosen was the combination of the /usr/share/doc and /usr/include folders, which together add up to 976 MiB spread across 69,646 small files — exactly the kind of load that stresses backup deduplicators the most.
The measured results for wall clock time, total CPU time (user + sys) and peak RAM (Maximum resident set size via wait4/rusage) were:
| Operation (69,646 files / 976 MiB) | Tool | Total time | CPU time | Peak RAM | Highlight |
|---|---|---|---|---|---|
| 1st initial backup (cold) | restic 0.19.1 |
2.89 s | 14.97 s | 441.2 MiB | Statistical tie on time |
| 1st initial backup (cold) | rustic 0.11.4 |
3.06 s | 17.60 s | 167.8 MiB | 62% less RAM |
| 2nd incremental backup (no changes) | restic 0.19.1 |
1.78 s | 2.27 s | 76.5 MiB | — |
| 2nd incremental backup (no changes) | rustic 0.11.4 |
1.05 s | 1.90 s | 143.9 MiB | 41% faster |
Integrity check (check) |
restic 0.19.1 |
0.86 s | 1.38 s | 91.6 MiB | — |
Integrity check (check) |
rustic 0.11.4 |
0.28 s | 0.49 s | 144.0 MiB | More than 3× faster |
Cleanup (forget --keep-last 1 --prune) |
restic 0.19.1 |
0.91 s | 1.27 s | 89.9 MiB | — |
Cleanup (forget --keep-last 1 --prune) |
rustic 0.11.4 |
0.60 s | 1.02 s | 145.5 MiB | 34% faster |
Notice the two points that matter most in real life:
- On the first heavy backup, while both tie on write speed (saturating the NVMe SSD in about 3 seconds),
resticneeded 441.2 MiB of RAM, against only 167.8 MiB forrustic(almost a third of the memory to index the same 69 thousand files!). In a real repository with millions of files (my full machine backup has almost 3 million files and 465 GiB on the data partition), this memory difference is brutal. - In the operations that run every day in the crontab — the incremental backup scanning the tree for changes, the integrity
checkandforget --prune—rusticwon all of them comfortably, being 34% to over 200% (3×) faster.
What does rustic have that restic doesn't?
And like any good salesman, I'll add: BUT WAIT, THERE'S MORE!
As documented in the official comparison table, rustic comes out of the box with a series of architectural features that solve long-standing restic pain points:
- Lock-free operations (no repository locks): remember that in my post about restic the script's first command was a
restic unlockto clear orphaned locks in case a previous backup had been interrupted? Inrustic, that simply doesn't exist. Its design is lock-free, allowing multiple clients to run operations on the same repository at the same time without corrupting anything and without blocking the next day's routine. - TOML configuration profiles (
rustic.toml): whilerestichas historically refused to have a native configuration file (forcing users to write scripts or use third-party tools),rusticreads complete profiles from/etc/rustic/*.tomlor~/.config/rustic/*.toml(selected with-P <profile>), and also supports native hooks before and after the backup. - Native Cold Storage support: it lets you work with repositories split between "hot" storage (
--repo-hot, holding only metadata and indexes for fast lookups) and very low-cost "cold" storage (such as AWS S3 Glacier), automatically handling the warm-up of packs when you need to restore or check data. - Much more powerful filters and
.gitignoresupport: it natively understands.gitignore,.ignoreandCACHEDIR.TAGfiles, acceptsjqexpressions directly to filter snapshots (--filter-jq), supports Git-style relative syntax (likelatest~2to get the third-to-last snapshot), backs up block devices directly and restores in place, modifying only the blocks that actually changed at the destination. - Dynamic pack sizes: avoids the classic problem of old
resticrepositories piling up hundreds of thousands of tiny files of a few megabytes, by scaling pack size as the repository grows.
rustic-only commands
Comparing the --help of both tools, we find several commands that exist natively in rustic and don't exist in restic:
Command in rustic |
What it does |
|---|---|
rustic repoinfo |
Shows a full X-ray of the repository: number and size of index and data files (packs), compression ratio, used blobs vs. those marked for deletion. |
rustic merge |
Merges multiple snapshots into a single consolidated snapshot (great for combining backups made in parts). |
rustic webdav |
Instantly starts a local WebDAV server so you can browse snapshots in your browser or file manager, without depending on FUSE (mount). |
rustic config / show-config |
Lets you change internal repository settings (such as compression level, version and pack size) and view the final configuration loaded from the TOML profiles. |
rustic snapshots -i / ls -i |
Opens an interactive visual interface right in the terminal (TUI) to browse dates and folder trees, compare versions and select files to restore without leaving the shell. |
rustic docs / completions |
Opens the local documentation and generates autocompletion scripts for Bash, Zsh or Fish. |
My daily routine on Manjaro and the .restic.sh script
Despite all this enthusiasm, those who follow the blog know I am cautious with critical infrastructure. That is why, for now, I am using rustic as a hands-on test only for managing the backups of my personal desktop running Manjaro Linux (as I mentioned on the About me page, Manjaro has been my desktop distro of choice for years).
The script runs every day and, out of sheer laziness to change the shortcuts and schedules that already existed, it still goes by the ironic name /home/giuliano/.restic.sh — even though, inside, there is no longer a single call to restic.
My routine has evolved a lot since that simple script from the first post. Today it freezes the root partition on Btrfs by creating a read-only snapshot (/.snap_root) to guarantee absolute consistency of the running operating system, exports the Docker container images and metadata, backs up separately by tags to two repositories (a local disk and a remote one in the cloud via rclone), applies the retention policy, verifies integrity with check, and even notifies me on my phone via ntfy whether everything went well or something failed.
Below is a simplified version of my current ~/.restic.sh, with anonymized paths:
#!/usr/bin/env bash # ============================================================================== # FULL BACKUP SCRIPT (SYSTEM + DATA + DOCKER) - RUSTIC & BTRFS # ============================================================================== LOG_FILE="/home/user/rustic_full_backup.log" PASSWORD_FILE="/secure/path/.password.txt" NTFY_URL="https://ntfy.example.work/my-secret-topic" notify_error() { local message="$1" echo "[ALERT] $message" curl -s -o /dev/null \ -H "Title: Backup Failed (Desktop)" \ -H "Tags: rotating_light,skull" \ -d "$message" "$NTFY_URL" } notify_success() { curl -s -o /dev/null \ -H "Title: Backup Completed (Desktop)" \ -H "Tags: white_check_mark,floppy_disk" \ -H "Priority: min" \ -d "Backup and verification routine finished without interruptions." "$NTFY_URL" } # 1. Freeze the root system (read-only Btrfs snapshot) sudo btrfs subvolume delete /.snap_root 2>/dev/null sudo btrfs subvolume snapshot -r / /.snap_root || notify_error "Failed to create Btrfs snapshot." # 2. Export Docker metadata and images DOCKER_BKP_DIR="/var/tmp/docker_bkp_temp" sudo mkdir -p "$DOCKER_BKP_DIR" if [ -n "$(sudo docker ps -aq)" ]; then sudo docker ps -a > "$DOCKER_BKP_DIR/containers_list.txt" sudo docker inspect $(sudo docker ps -aq) > "$DOCKER_BKP_DIR/containers_inspect.json" 2>/dev/null fi if [ -n "$(sudo docker images -q)" ]; then sudo docker save $(sudo docker images -q) -o "$DOCKER_BKP_DIR/docker_images.tar" 2>/dev/null fi # 3. Repositories: cloud (Google Drive via rclone) and local disk REPOS=( "rclone:my_remote:backup/desktop" "/mnt/armazem/backup" ) # 4. Run for each repository (note that we no longer need 'unlock'!) for REPO in "${REPOS[@]}"; do # 4.1 Operating system backup (from the frozen Btrfs snapshot) cd /.snap_root || notify_error "Could not access /.snap_root" sudo rustic --password-file="$PASSWORD_FILE" -r "$REPO" backup . \ --tag "system-root" \ --one-file-system \ --glob "!./tmp/*" \ --glob "!./var/tmp/*" \ --glob "!./timeshift" || notify_error "ROOT SYSTEM backup error in: $REPO" # 4.2 Backup of the Sync folder only cd / sudo rustic --password-file="$PASSWORD_FILE" -r "$REPO" backup /home/user/Sync \ --tag "sync-folder" \ --exclude-if-present CACHEDIR.TAG \ --glob "!**/.cache" || notify_error "SYNC folder backup error in: $REPO" # 4.3 Personal data backup sudo rustic --password-file="$PASSWORD_FILE" -r "$REPO" backup /home /armazem \ --tag "personal-data" \ --glob "!/armazem/backup" \ --exclude-if-present CACHEDIR.TAG \ --glob "!**/.cache" || notify_error "PERSONAL DATA backup error in: $REPO" # 4.4 Docker backup (volumes, images and configs) sudo rustic --password-file="$PASSWORD_FILE" -r "$REPO" backup \ /var/lib/docker/volumes "$DOCKER_BKP_DIR" \ --tag "docker-data" || notify_error "DOCKER backup error in: $REPO" # 4.5 Retention policy, pruning and integrity check sudo rustic --password-file="$PASSWORD_FILE" -r "$REPO" forget \ --keep-daily 7 --keep-weekly 2 --keep-monthly 1 --keep-tags keep --prune \ || notify_error "forget/prune error in: $REPO" sudo rustic --password-file="$PASSWORD_FILE" -r "$REPO" check \ || notify_error "WARNING: INTEGRITY failure in: $REPO" done # 5. Final cleanup cd / sudo btrfs subvolume delete /.snap_root sudo rm -rf "$DOCKER_BKP_DIR" notify_success
An important detail for anyone migrating a script from restic to rustic: although the main commands (backup, forget, prune, check, restore) have the same names, some exclusion and retention flags change. In my migration, I had to replace --exclude="folder" with --glob "!folder", swap --exclude-caches for --exclude-if-present CACHEDIR.TAG and change --keep-tag (singular) to --keep-tags (plural).
On the other hand, the output of rustic forget in the log is a show of its own, drawing very clear tables grouped by host, tags and paths, showing exactly why each snapshot was kept (daily, weekly, monthly, tags) or removed:
[INFO] snapshots for (host [desktop], label [], paths [/armazem,/home]): | ID | Time | Host | Label | Tags | Paths | Action | Reason | |----------|---------------------|---------|-------|---------------|----------|--------|---------| | c4e70438 | 2026-11-12 08:29:00 | desktop | | personal-data | /armazem | keep | daily | | | | | | | /home | | weekly | | | | | | | | | monthly | | 6bee4c53 | 2026-11-12 03:04:49 | desktop | | personal-data | /armazem | remove | | | | | | | | /home | | | | be401c6e | 2026-11-11 13:12:32 | desktop | | personal-data | /armazem | keep | daily |
Not everything is rosy: rclone vs. OpenDAL, documentation and the ecosystem
Since no tool is perfect, it is important to point out where rustic still falls short of the veteran restic.
1. Why I keep using rclone for Google Drive (and OpenDAL's limitations)
If you noticed in the script above, my cloud repository is still accessed via rclone:my_remote:backup/desktop. But doesn't rustic have native support for dozens of cloud services?
Sort of. rustic delegates access to most remote backends to the Apache OpenDAL library (Open Data Access Layer), using the opendal:<service> syntax (like opendal:s3 or opendal:gdrive), besides keeping direct integration with rclone.
I deliberately chose not to use rustic's native Google Drive support and keep going through rclone. The reason is simple: I trust my Google Drive remote preconfigured in rclone — which has years of mileage handling rate limits, OAuth2 token renewal, recursive listings and the quirks of Google's API — much more than OpenDAL's generic layer.
And here come other drawbacks of this library's coverage: although OpenDAL works very well for standard object storage like Amazon S3, there is no real parity in maturity and features across all listed backends. Just look at rustic's own table of supported services: SFTP support via OpenDAL doesn't even work on Windows, FTP support had to be disabled due to build problems ("FTP is temporarily deactivated, due to build issues"), and on consumer cloud services (like Google Drive itself, OneDrive or Dropbox) users frequently run into severe performance bottlenecks when listing thousands of packs recursively, limitations in atomic operations, or subtle inconsistencies when reading metadata during check. rclone, on the other hand, is rock solid.
2. Documentation that outsources the work
Another point where the rustic manual loses to the restic documentation is coverage. The restic manual is direct, complete and self-contained: everything you need to know to authenticate and configure any backend is explained step by step on the page itself.
The rustic documentation, on the other hand, in several places — especially on the remote services configuration page (services.html), when you want to set up Google Drive, Backblaze B2, SFTP or other remotes via OpenDAL — gives only a basic S3 example and simply tells you to read the library's technical documentation at docs.rs/opendal/latest/opendal/services/ to find out which parameters go in the [repository.options] block, instead of providing the information and ready-made examples on the documentation page itself. For anyone who just wants to set up a backup quickly without having to dig through method and struct names in a Rust crate's documentation (like opendal::services::Gdrive), that is an unnecessary nuisance.
3. An "Awesome Rustic" is missing
Finally, the last drawback (natural, given the age difference between the projects) is the ecosystem around the tool.
When I wrote about restic, I made a point of recommending the excellent awesome-restic repository, which gathers dozens of GUIs (like Backrest, Restic Browser and NPBackup), profile managers (like resticprofile), metrics exporters and community-made scripts that greatly expand backup management.
Although rustic already includes some things that in restic depended on external projects (such as TOML profiles, native metrics export to Prometheus and OpenTelemetry, and the interactive terminal TUI), we still miss GUI tools and third-party utilities made specifically to extend its management. Thanks to the compatible repository format, several restic tools can read a repository managed by rustic, but a real "awesome-rustic" is still missing.
Conclusion
Even though I am keeping rustic on a daily trial basis on my Manjaro desktop for now, and prefer the safety of good old rclone to talk to Google Drive, the balance so far is extremely positive. Having a single Rust binary, much faster at daily checks, using a fraction of the RAM, free of those annoying locks (unlock) and 100% compatible with the restic repositories I already had is the best of both worlds.
If you already use restic in your homelab or Linux desktop, I strongly recommend installing and testing rustic (GitHub) on an existing repository — doing a --dry-run test first, of course. It is one of those tools that show how the open-source ecosystem evolves when someone decides to scratch their own itch competently.