Friday evening the team refreshes staging: rsync prod → staging with --delete to mirror production exactly. Monday morning staging is correct — but production shows missing files. A mis-commented cron reversed direction: staging to prod. rsync wiped production to match partially empty staging.
rsync is the go-to tool for fast mirrors and file-based deploys. It is also symmetric in danger: the same flags that sync cleanly can destroy years of files in one command. Trailing slash, --delete, SRC→DST direction, and excludes are not details — they are the line between a healthy refresh and a P1 incident.
Trailing slash: the rule to tattoo
rsync -av prod:/var/www/ staging:/var/www/
# copies CONTENTS of /var/www/
rsync -av prod:/var/www staging:/var/www/
# creates staging:/var/www/www — nested nesting
With trailing slash on source, rsync copies directory contents. Without slash, it copies the directory itself into destination. The www/www mistake is a staging refresh classic.
Document convention in every script: comment # SRC=prod:/var/www/ DST=staging:/var/www/ with explicit arrows.
--dry-run and --delete
Always -n or --dry-run before any sync with --delete. Then --itemize-changes for readable audit. Any *deleting line in output = mandatory pause, direction re-read, dated backup.
--delete removes on destination what no longer exists on source. Acceptable only after validated dry-run, dated destination backup, written direction, two-person review.
Never --delete on cron without exit code monitoring and mail alert on non-zero.
Direction, flock, and backup-dir
| Need | Safe pattern |
|---|---|
| Staging refresh | prod → staging, delete staging only |
| DR mirror | prod → DR, periodic checksum -c |
| Deploy | CI artifact, not full prod rsync |
flock on cron prevents overlapping syncs. --backup --backup-dir=dated/ before mass delete — saves timestamped copy of removed files.
Explicit RSYNC_RSH=ssh; harmonize GNU vs BSD rsync versions across servers before automation.
Excludes and pitfalls
Git-versioned --exclude-from file: .env, storage/sessions, uploads, runtime caches, node_modules. Never sync cache to prod nor unsanitized secrets to staging.
--delete-excluded is extremely dangerous — avoid except documented expert cases.
rsync also propagates ransomware: rsync mirror alone is not backup strategy. Pair immutable restic or Borg for history; rsync for mirror speed.
Monitoring and training
Alias rsync-safe='rsync -av --dry-run --itemize-changes' for all ops. Removing dry-run = conscious signed act.
Post-sync: script diff file count source/destination. Cron mail on non-zero exit — silent fail classic Friday night.
30-minute onboarding: trailing slash quiz, reversed direction post-mortem reading. Two crons opposite directions = eventual data loss — mandatory architecture review.
Pair with versioning
Rsync mirror for speed, restic or Borg for history — document which is source of truth for restore. Never tell auditor rsync alone is backup strategy.
Bi-directional rsync two cron jobs opposite direction equals data loss eventually — architecture review any bidirectional sync.
File ownership numeric IDs differ across servers — rsync -a preserves, breaks if www-data UID differs.
Operational follow-up
Rsync ops onboarding training — 30 minutes prevents weekend incident. Trailing slash quiz exercise. Cron mail on rsync non-zero exit — silent fail classic Friday night. Document gaps between host marketing and field measurement in the quarterly review.
Quarterly follow-up
Rsync ops onboarding training — 30 minutes prevents weekend incident. Trailing slash quiz exercise. Cron mail on rsync non-zero exit — silent fail classic Friday night. Document gaps between host marketing and field measurement in the quarterly review.
Post-sync file count diff script catches partial transfers before users notice missing assets.
Keep a dated runbook, before/after metrics, post-incident review — cumulative discipline beats Friday night panic.
Keep a dated runbook, before/after metrics, post-incident review — cumulative discipline beats Friday night panic.
Keep a dated runbook, before/after metrics, post-incident review — cumulative discipline beats Friday night panic.
Decide and move forward without blind spots
- rsync-safe alias with --dry-run — removing dry-run = conscious signed act.
- Tattoo trailing slash — SRC with
/copies contents; without/creates nested nesting. - SRC → DST direction in comment — every cron script; flock against overlap.
- Git-versioned excludes — .env, sessions, uploads; never --delete-excluded without review.
- Pair rsync mirror + immutable versioned backup — rsync alone propagates ransomware.
Deploy and DR: guides, directory, comparison tool.
Frequently asked questions
What does trailing slash on source change?
With /: rsync copies directory contents. Without /: nested subdirectory — classic staging/www/www mistake.
When is --delete acceptable?
After validated dry-run, dated backup, written SRC→DST, two-person review. Never on cron without exit code monitoring.
Should cache and node_modules be excluded?
Yes via versioned --exclude-from. Do not sync runtime cache to prod nor .env or sessions.
Does rsync replace versioned backup?
No: rsync also propagates ransomware. Pair immutable backup (restic, Borg) with rsync mirror.
Default aliases to --dry-run — removing it must be a conscious act.
