Backup tool research: restic versus a minimal rsync design

Reviewed 30 August 2026 against the existing backup.sh, the previous research, current upstream documentation, and tagged source trees.

Answer

Restic is not the best choice if minimizing tools and code is the primary requirement and you have an independent SSH backup server. In that case, keep rsync and build snapshots from ordinary directories with --link-dest. This adds only a small shell wrapper and makes recovery possible with cp or rsync.

Restic remains the best practical compromise if Mega must remain the destination and you want encrypted, space-efficient snapshots, safe retention, browsing, and integrity checks. Rsync cannot use Mega, while rclone alone is transport rather than a complete backup engine. Restic is operationally compact—a single static executable—but its implementation is not suckless-small.

My preferred order is therefore:

  1. Independent SSH server available, ordinary directory snapshots acceptable: rsync + SSH + hard links.
  2. Mega remains primary: restic + the existing rclone Mega transport.
  3. SSH server available, but chunk deduplication, authenticated encryption, and repository checks are required: Borg, or restic over native SFTP.

Keep rsync for server synchronization regardless. Synchronization and backup are different jobs.

The important correction to the previous recommendation

The previous report optimized for the current Mega backend and a complete backup feature set. Under those assumptions, restic was correctly the best fit. It did not give enough weight to the user’s stronger requirement: minimize added programs, source code, opaque formats, and operational concepts.

Restic is minimal at deployment time, not in implementation:

Those features are useful and cohesive for a backup repository, but this is not a small, easily audited Unix tool. The static binary hides runtime dependency management; it does not remove the compiled dependency and audit surface.

What a minimal rsync backup would look like

Run the backup from the backup server, pulling data over SSH. Create each snapshot in a staging directory and publish it only after rsync succeeds:

/backups/workstation/
  2026-08-29T010000Z/
  2026-08-30T010000Z/
  latest -> 2026-08-30T010000Z
  .staging-1234/

The essential transfer is:

rsync -aHAX --numeric-ids --delete --delay-updates \
  --link-dest=/backups/workstation/PREVIOUS \
  workstation:/source/ /backups/workstation/.staging-1234/

After success, rename the staging directory to its timestamp and atomically replace latest. Never update an already-published snapshot, and never combine this design with --inplace: rsync warns that in-place updates can modify hard-linked files and damage snapshot history.

Why this is attractive

  • No new backup format. Each snapshot is an ordinary directory.
  • No new backup engine. The user already operates rsync and SSH.
  • Simple restore. Browse directly, then rsync -aHAXn for a dry run and rsync -aHAX to restore.
  • Simple failure model. An incomplete staging directory is not a published recovery point.
  • Whole-file deduplication. Unchanged files are hard links and consume almost no additional data blocks.
  • Small policy layer. A short local script owns locking, naming, retention, and logs.

The official rsync site itself publishes a rotating seven-day incremental-backup example, and --link-dest is the supported mechanism for hard-linking unchanged destination files.

Costs that must be accepted

  • A changed file consumes a complete new destination file, even though rsync may transfer only changed blocks. Restic/Borg can store only changed chunks inside a large file. This matters for frequently changed browser files, VM images, container data, and SQLite databases.
  • SSH encrypts traffic, not the destination disk. Use an already-encrypted filesystem such as LUKS if at-rest encryption is required.
  • There is no authenticated encrypted repository or built-in repository-wide check. Periodic destination-side SHA-256 manifests can detect bit rot only if the manifests are protected separately.
  • Retention remains script policy. Delete only completed snapshots, retain the latest successful snapshot, and do not delete good history merely because the new run ran out of space.
  • Hard links require all snapshots to be on the same filesystem. Attribute differences can prevent linking.
  • Ordinary snapshots are mutable. A separate backup-server account, read-only restore access, filesystem snapshots, or an offline copy is needed for stronger ransomware protection.

These are not small footnotes: they are the functionality removed by choosing fewer tools.

Security architecture matters more than the command

A pull backup is preferable: workstation → read-only SSH/rsync → backup server.

The backup server owns the SSH key and pulls from a restricted read-only source account. The workstation has no credential that can erase old snapshots. OpenSSH’s restrict and forced-command options, together with rsync’s rrsync -ro, can constrain that account.

A push design gives a compromised workstation credentials to its backup destination. Restic encryption does not prevent a holder of write/delete credentials from deleting the repository; restic’s own threat model says this explicitly. Mega account history or a second offline copy is still necessary.

SQLite and Podman are unchanged by the tool choice

Neither rsync nor restic makes a transactionally consistent copy of a live SQLite file. SQLite explicitly warns that a background file copy can combine old and new content. Preserve the existing use of SQLite’s online .backup, VACUUM INTO, or the newer sqlite3_rsync utility. Validate representative restores with PRAGMA integrity_check.

Rootless container volumes still require podman unshare, and application data may need quiescing. A backup program cannot manufacture application-level consistency.

Measured implementation size

These figures are orientation, not a quality ranking. They count physical lines—including comments and blanks—in tracked implementation files, while excluding tests, documentation, examples, generated/vendor trees, and build output. Multi-repository libraries are included where they form part of the product. Exact comparisons across C, Go, Python/Cython, Rust, Perl, and shell are inherently imperfect.

Candidate Version/commit Approx. implementation lines Deployment/fit observation
No new engine: local wrapper project-specific likely well under 300 Reuses rsync/SSH; ordinary files; policy remains ours
rsync-time-backup 9b3ea2d 638 Tiny, but current SSH construction disables host-key verification and uses eval; use as reference, not unchanged
rsnapshot e7d7820 7,861 main Perl program Mature rsync wrapper; more rotation/config machinery than needed
bup 0.33.10 20,262 Lean content chunking, but no integrated repository encryption and upstream admits important maturity gaps
Borg 1.4.5 43,359 Narrow local/SSH focus; authenticated encryption, chunk dedup, checks; standalone binary around 30 MB
restic 0.19.1 48,675 Broad backends and complete repository model; static binary around 31 MB
rustic CLI + core/backend libraries 0.11.4 family about 37,000 Smaller own CLI, but default features include TUI, WebDAV, Prometheus, OpenTelemetry, and jq; 757 packages appear in its lockfile
Kopia 0.23.1 over 110,000 Strong but clearly too broad here; rclone support is documented as experimental
rclone 1.75.0 over 330,000 Already needed for Mega, but very broad; not a snapshot engine

The tagged trees used for direct measurements were cloned shallowly. A representative command was:

git ls-files '*.go' |
  grep -vE '(^|/)(testdata|tests?)/|_test\.go$' |
  xargs wc -l

Equivalent implementation suffixes and exclusions were used for each language. Rustic’s total includes the separately versioned rustic_core and rustic_backend libraries used by the CLI; counting only the CLI repository would misleadingly report about 11,927 lines.

A crucial perspective: modern rsync itself is not tiny (roughly 115,000 implementation lines by a similar broad measurement). Its advantage here is zero marginal tool count, an established operational role, a transparent output format, and a narrower backup wrapper—not that rsync’s protocol implementation is small.

Alternatives

Restic

Choose for Mega. It has an officially documented rclone backend and starts rclone itself. It supplies exactly the capabilities missing from the current dated-folder script: encrypted/authenticated objects, content-defined chunk deduplication, complete snapshots, browse/diff/restore, retention, locking, interruption-safe repository ordering, and check.

Its repository is intentionally opaque without restic and the password. It also needs forget, occasional prune, checks, and restore drills. It cannot protect a repository from deletion by an attacker with storage credentials.

Borg

Choose when an SSH backup host exists and full repository features are worth one new tool. Borg’s scope is narrower than restic’s: local and SSH repositories rather than many clouds. It provides chunk deduplication, compression, authenticated encryption, archive mounting/extraction, and integrity checks. Efficient remote operation normally installs Borg on both ends, unlike rsync which is already present in the user’s workflow and restic’s SFTP backend which needs only SSH/SFTP remotely.

Rustic

Do not replace restic with rustic merely to chase a lower line count. Rustic implements the restic format and supports rclone/SFTP, but the current default build includes a TUI, WebDAV server, Prometheus, OpenTelemetry, and jq functionality. It is a faster-moving, less mature choice and conflicts with the stated wish to avoid breadth. A deliberately feature-minimized custom build is possible, but maintaining a custom build profile adds complexity rather than removing it.

bup

Bup is the most interesting lean chunk-deduplicating option and works over SSH. However, it has no integrated encrypted repository, depends on Python/C/Git tooling, describes itself as less tested than tar and as missing important features, and has immature metadata caveats. It is not the right primary private off-site backup.

rsnapshot and rsync-time-backup

Rsnapshot is reasonable if a maintained, configurable rsync rotation wrapper is preferred over a local script. Its nearly 8,000-line Perl program and interval semantics buy little for this specific source list.

Rsync-time-backup is only 638 lines and produces the desired transparent snapshots. Its current script, however, constructs remote commands with eval and passes StrictHostKeyChecking=no plus UserKnownHostsFile=/dev/null. That is a poor default for a backup trust boundary. A smaller purpose-built wrapper using normal argument boundaries and normal SSH host verification is easier to audit.

Rclone alone

Rclone should remain storage plumbing, not the backup model. It has no content deduplication, snapshot catalogue, or retention graph. The current dated-directory scheme duplicates trees and owns unsafe retention logic. Mega also lacks modification times and hashes, allows duplicate names, and may temporarily block repeated rclone logins. Restic’s long-lived rclone service and internal hashes reduce some issues, but do not make Mega an ideal repository backend.

Final recommendation for this machine

If an independent server can hold the backups

Replace the Mega-primary design with a backup-server pull using rsync --link-dest. Keep the wrapper deliberately small and explicit:

backups.sh create
backups.sh list
backups.sh restore SNAPSHOT PATH TARGET
backups.sh check
backups.sh prune

list can be directory listing, restore can call rsync, check can validate protected SHA-256 manifests, and prune can implement one simple retention policy. Do not add configuration frameworks or generic backend abstraction.

Before choosing it, measure one week of changed data. If frequently modified large files make hard-link snapshots too large, that is concrete evidence to use Borg/restic chunk deduplication rather than a hypothetical feature argument.

If Mega must remain

Keep the previous restic + rclone recommendation. Do not switch to rustic, Kopia, Duplicity, or a home-grown encrypted chunk store. Restic is larger than the philosophy prefers, but replacing its cryptography, interruption safety, indexes, retention, and verification with shell code would be false minimalism: fewer installed binaries but a larger, riskier system owned locally.

Pilot it before migration, retain the current backups during overlap, and prove selective and full restores. If Mega behaves badly, change storage—not the restic repository model—to native SFTP or S3-compatible object storage.

Decision in one line

Use rsync snapshots when you control a backup server; use restic when you must turn Mega/rclone into a real encrypted, deduplicated backup repository.

Primary sources