Guide

Dated restore points on plain storage, with one rsync flag.

A mirror tells you what your server looks like now — including now's mistakes. Hardlink snapshots give you every day's version, at roughly the cost of one copy plus the daily changes.

How the trick works

--link-dest=DIR tells rsync: when a file is unchanged compared to DIR, don't copy it — create a hardlink to the existing copy. A hardlink is another directory entry pointing at the same data on disk, so it costs essentially nothing. Point --link-dest at yesterday's snapshot, write today's into a fresh dated directory, and you get:

daily/2026-07-08/   ← full tree, browsable
daily/2026-07-09/   ← full tree, browsable
daily/2026-07-10/   ← full tree, browsable

Every directory looks and restores like a complete backup, but files unchanged across days exist on disk once. Thirty days of a 200GB server with 2% daily churn stores roughly 200GB + 30×4GB ≈ 320GB — not 6TB. Deleting any snapshot never breaks the others; the data survives until the last hardlink to it is gone.

A complete rotation script

#!/bin/bash
# /usr/local/bin/snapshot-backup.sh — hardlink snapshots to RSYNCIT
set -euo pipefail

REMOTE="usr1042@usr1042.rsyncit.com"
KEY="/root/.ssh/rsyncit"
TODAY=$(date +%F)
KEEP_DAYS=30

# 1. today's snapshot, hardlinked against the most recent one
LATEST=$(ssh -i "$KEY" "$REMOTE" 'ls -1d daily/????-??-?? 2>/dev/null | tail -1' || true)

rsync -a --delete \
  -e "ssh -i $KEY" \
  ${LATEST:+--link-dest="/home/usr1042/$LATEST"} \
  --exclude-from=/etc/backup-excludes.txt \
  /etc /home /var/www \
  "$REMOTE:daily/$TODAY/"

# 2. rotate: drop snapshots older than KEEP_DAYS
ssh -i "$KEY" "$REMOTE" \
  "ls -1d daily/????-??-?? | head -n -$KEEP_DAYS | xargs -r rm -rf"

Note --link-dest takes a path as seen from the destination — absolute, or relative to the target directory. The most common failure is handing it a path relative to the wrong side and silently re-uploading everything; if a run transfers far more than your daily change, check that path first.

Restoring

This is the payoff: restores are just rsync (or SFTP, or a WebDAV browse) in the other direction, from whichever date you need. rsync -a "$REMOTE:daily/2026-07-03/etc/nginx/" /etc/nginx/ pulls Tuesday's nginx config. No restore software, no catalog, no vendor — the snapshot is a directory tree, readable by anything. That property is worth more than most backup features.

Pitfalls worth knowing

  • Metadata changes break the link. A file whose permissions or owner changed gets stored fresh even if contents match — expected, occasionally surprising in size terms after a mass chmod.
  • Renames are copies. Hardlink matching is by path; renaming a 50GB directory stores it again. Tolerable for configs and code, worth knowing for media libraries.
  • Rotation edge: the head -n -N rotation assumes the date-sorted listing is the whole story — keep snapshot names machine-generated and don't hand-create directories in daily/.
  • Databases: same rule as always — dump first, snapshot the dump (see the base guide).

Storage that hardlinks happily

Snapshots need a real filesystem on the far end — that's exactly what RSYNCIT is. Flat $6.95/TB, unmetered uploads for the day-one seed.

See pricing

TL;DR

  • Dated dirs, hardlinked against yesterday
  • Each snapshot browsable & independently deletable
  • Cost ≈ one copy + daily changes
  • Restore = rsync the other way
  • --link-dest path is destination-relative