There is a category of backup need that the chunk-deduplicating tools of the previous two articles answer with more machinery than the job asks for: the local machine, the attached disk, the on-site copy whose consumer is a human browsing directories on a file manager. Rsnapshot serves that category with a mechanism that reads like a trick: each snapshot is a complete, browsable directory tree, every snapshot looks like a full copy, and yet a hundred snapshots occupy barely more space than one, because the unchanged files are hardlinks, one physical inode appearing under a hundred names. This article builds the full setup: the hardlink mechanics, the configuration file and its rotation intervals, the rsync parameters underneath, local and remote sources, the honest comparison against the chunk-deduplicating siblings, and the scheduling and verification that keep the rotation honest.
The hardlink trick and the economics it creates
A hardlink is a directory entry pointing at an existing inode, and a file with a hundred hardlinks costs one inode and one copy of its data. Rsnapshot's mechanism exploits this directly: the first snapshot copies the source fully, and every subsequent snapshot creates its directory tree by linking unchanged files to the previous snapshot's inodes and copying only the files that changed. The result is that each snapshot directory is complete and independent-looking, a full tree a human or a script can walk into, while the disk cost of a new snapshot is proportional to the change since the previous one.
The economics deserve the concrete example, because they are the tool's whole reason to exist. A server with fifty gigabytes of data whose daily change is two hundred megabytes keeps an hourly rotation of six, a daily rotation of seven, a weekly of four and a monthly of twelve: twenty-nine complete snapshots, every file of every one browsable, on a disk holding roughly fifty gigabytes plus twenty-nine days of cumulative change, a figure that would make a naive copy-based scheme require a terabyte and a half. The restore story is the mirror of the browsing story, the file from three Tuesdays ago is a file at a path, cp recovers it, and the recovery is instant because it never leaves the local disk.
The configuration file and its strict grammar
Rsnapshot's configuration lives at /etc/rsnapshot.conf, and the first lesson about it is its punctuation: the file uses tabs between fields, not spaces, and the parser rejects space-separated lines with a message that is famous precisely because it surprises everyone once. The configtest command is the preflight that belongs to every change:
$ rsnapshot configtest
Syntax OK
The skeleton of a working file, the parts that matter:
snapshot_root /srv/backups/
cmd_cp /bin/cp
cmd_rsync /usr/bin/rsync
cmd_ssh /usr/bin/ssh
retain hourly 6
retain daily 7
retain weekly 4
retain monthly 12
backup /etc/ localhost/
backup /home/ localhost/
backup root@db01:/etc/ db01/
backup root@db01:/srv/ db01/
The snapshot root is the destination, the retain lines define the rotation intervals and their depths, and the backup lines name the sources and the per-host subdirectory. The intervals run as separate invocations, rsnapshot hourly performs the hourly step, and the rotation arithmetic is the next section's subject.
Rotation, the shift that keeps every interval honest
The rotation is a shift, and its logic is worth walking through once because every subsequent behavior follows from it. With hourly 6 configured, the snapshot root holds hourly.0 through hourly.5, and hourly.0 is always the newest. When the hourly step runs, the oldest, hourly.5, is deleted, each remaining directory shifts up by one, hourly.4 becomes hourly.5, and the new snapshot is created as hourly.0, linked against the previous hourly.0, now hourly.1. The same shift runs at each interval, and the intervals cascade: rsnapshot daily rotates the daily directories, creating daily.0 by linking against the most recent hourly before shifting it, which is why the standard cron schedule runs hourly twelve times a day, daily once at midnight, weekly on Sunday, monthly on the first, each interval feeding the next.
The disk budget is the retention arithmetic from the example above, and the tool provides the honest measurement:
$ rsnapshot du
which walks the snapshot trees and reports their actual disk consumption, the hardlink-aware number, the one that tells the administrator whether the budget's assumption about change rate matches reality. A quarterly review of the du output against the disk's free space is the maintenance cadence that keeps the rotation's economics on the rails, because the change rate that grows, the log directory that creeps, quietly grows every snapshot by a bit more until the review catches it.
The rsync underneath and the parameters that matter
Rsnapshot is a careful wrapper around rsync, and the parameters it passes are the tool's tuning surface. The defaults implement the hardlink mechanism through the link-dest rsync option, which tells each new snapshot's transfer to link unchanged files against the previous snapshot rather than copying them, and the archive flags carry the standard set, permissions, ownership, timestamps, the whole metadata vocabulary. The configuration's rsync_long_args and rsync_short_args override or extend the set when the site needs it, and the two common additions are the bandwidth limit for the remote sources, --bwlimit=2048 keeping the nightly pull from saturating the office line, and the rsync exclude patterns, which carry the same runbook discipline the rest of the series demands, every excluded tree a documented decision.
The remote sources use the cmd_ssh path with the SSH key authentication the bastion article established, and the pull direction is a design choice worth its sentence: rsnapshot pulling from the clients means the backup host holds the credentials, the clients hold no outbound trust, and a compromised client cannot push anything to the backup, the architectural inversion of the push-based tools that suits the posture where the backup host is the hardened one.
The honest comparison against the chunk-deduplicating siblings
The comparison with restic and Borg is a decision about the consumer and the custody rather than about quality, because both mechanisms are excellent at what they do. Rsnapshot's strengths are the browsable, plain-filesystem snapshots, the restore that is a cp from a path, and the zero-learning-curve readback, any team member with file-manager skills can find the file from last Tuesday. Its boundaries are the ones the mechanism implies: the hardlinks must live on one filesystem, the snapshots are bound to the disk they were made on, there is no encryption, so the disk's physical security is the data's security, and the deduplication is per-path rather than per-content, so moving a fifty-gigabyte tree to a new path costs fifty gigabytes while the same move in a content-addressed repository costs nothing.
The decision pattern that fits both into one architecture: the chunk-deduplicating repository is the off-site, encrypted, long-retention layer, the rsnapshot rotation is the local, browsable, fast-recovery layer, and the two answer different questions on the recovery-time spectrum, the local hardlink tree answering in minutes because it never leaves the disk, the remote encrypted repository answering the questions the local disk cannot survive. The series' backup stack is complete with both layers, and the same quarterly restore rehearsal exercises each at its own altitude.
Scheduling, logging and the verification habits
The cron schedule is the classic four-line pattern, and the standard layout names the intervals explicitly:
0 root /usr/bin/rsnapshot hourly
50 23 * root /usr/bin/rsnapshot daily
40 23 0 root /usr/bin/rsnapshot weekly
30 23 1 root /usr/bin/rsnapshot monthly
each interval's completion separated from the next by the ten-minute spacing that avoids the lock contention the previous article described, and the log configuration writing each run's rsync statistics to the loglevel and logfile of the config. The verification is the two-signal pattern from across the series: the newest snapshot's age, checked against the wall clock by a monitoring script or a simple find against hourly.0, and the occasional content check, a file pulled from the oldest rotation and compared against the source's history, the lightweight cousin of the restore rehearsal.
The lock, the pre-rotation sanity check, and the configtest after every configuration change complete the operational hygiene, and the closing checklist is the working summary: config with tabs, intervals with depths, sources with per-host directories, schedule with spacing, monitoring on the newest snapshot's age, du on the quarterly calendar, and the restore test that is a cp and should therefore never be skipped, because the test that costs nothing is the one with no excuse, and the backup whose restore was never tested is only half a backup.
Script hooks and the database exception
The one category of data that rsnapshot cannot copy correctly from a live system is the database, the file set that is only consistent when the engine says so, and the tool's answer is backup_script, the hook that runs a command before the snapshot and includes the command's output in the tree:
backup_script /etc/rsnapshot/scripts/dump-db.sh db01/dumps/
The script's contract is simple: write its artifacts to the current working directory, which rsnapshot places inside the snapshot being created, and the dumps land in the rotation like any other file. The standard dump script pairs the engine's dump command with the compression the disk budget wants:
#!/bin/bash
set -euo pipefail
pg_dumpall | zstd -3 > db-full-$(date +%F).sql.zst
find . -name 'db-full-*.zst' -mtime +1 -delete
the second line's cleanup keeping the per-snapshot working directory from accumulating duplicates, because rsnapshot's linking works on the rsync-copied trees while the script outputs are fresh files each run, the operational detail that distinguishes the two halves of the snapshot. The same hook pattern serves the application-level consistency needs beyond databases, the cache flushed, the index exported, and the hook list belongs in the runbook with the same review cadence as the exclusion list.
Walking the tree, what the rotation looks like on disk
The snapshot root after a month of the standard schedule is a structure worth walking once, because its shape explains every behavior described above:
/srv/backups/
hourly.0/ localhost/ db01/
hourly.1/ localhost/ db01/
hourly.2/ localhost/ db01/
...
hourly.5/ localhost/ db01/
daily.0/ localhost/ db01/
...
daily.6/
weekly.0/ weekly.1/ weekly.2/ weekly.3/
monthly.0/ monthly.1/
Each numbered directory is a complete tree, every host a subdirectory, every source path a real directory underneath, and the hardlinking is invisible to the walker, the du command being the only place the shared inodes show up as saved space. The browsing experience is the property that keeps this tool in production use two decades after its introduction: the newest snapshot is a current mirror, the oldest monthly is a time machine, and everything between is navigable by the same cd and ls that every team member already knows, with no special tooling between the human and the file.
The closing frame on the backup trio
Rsnapshot completes the backup trilogy that the restic and Borg articles began, and the three tools together cover the recovery spectrum completely: the local browsable rotation for the fast human recovery, the encrypted chunk-deduplicated repository for the off-site long-retention layer, and the choice between the latter two being a matter of custody posture and append-only requirements rather than of quality. The fleet that runs the local rotation on every significant host and the encrypted repository on the central schedule has answered every recovery question with a tested path, and the quarterly rehearsal that exercises each layer at its own altitude keeps all three answers honest, which is the discipline that turns backup tools from installed software into recovery capability.