Archiving, syncing and backups
tar and compression formats, rsync options and semantics, incremental snapshots, and a backup you have actually restored.
tar and compression
tar -czf app-2026-09-18.tar.gz -C /srv app # archive a directory
tar -tzf app-2026-09-18.tar.gz | head # list without extracting
tar -xzf app-2026-09-18.tar.gz -C /tmp/restore # extract somewhere else
tar --exclude='node_modules' --exclude='*.log' -czf backup.tar.gz -C /srv app
tar -cJf archive.tar.xz big-directory # xz: smallest, slowest
tar -c --zstd -f archive.tar.zst dir # zstd: fast and small
tar -cf - dir | zstd -T0 > dir.tar.zst # compress in parallel
id # note the numeric uid/gid of the owner
tar --numeric-owner -xvzf backup.tar.gz # restore ownership as recorded| Format | Speed | Ratio | Use it for |
|---|---|---|---|
.tar | Fastest | None | Piping into another tool |
.tar.gz | Fast | Good | The portable default |
.tar.zst | Fast, parallel | Better than gzip | Large archives you compress often |
.tar.xz | Slow | Best | Long-term storage of rarely read data |
.zip | Moderate | Good | Interchange with Windows or macOS users |
-Csets the directory before the paths are read, which keeps extracted trees free of absolute paths and leading./noise.- Never extract a third-party archive as root into
/; unpack into a scratch directory and inspect it first, since archives can contain..paths and absolute links. - Archives are not backups on their own: a single corrupt byte can cost the whole file, so keep several generations.
rsync and what the trailing slash means
rsync -av --progress src/ dest/ # contents of src into dest
rsync -av src dest/ # the directory src itself, inside dest
rsync -av --delete --dry-run ./public/ deploy@web:/var/www/public/
rsync -av --delete ./public/ deploy@web:/var/www/public/
rsync -avz --partial --append-verify big.iso deploy@web:/srv/
rsync -av -e "ssh -p 2222" ./data/ deploy@web:/srv/data/
rsync -av --exclude-from=.rsyncignore ./ ./backup/
# incremental snapshots: unchanged files become hard links, storage stays flat
rsync -av --link-dest=../2026-09-17 ./ /backups/2026-09-18/⚠️
--delete removes files on the destination that are missing from the source, and one missing slash changes what the source is. Combine the two wrongly and you delete the wrong tree. Always run the exact command with --dry-run first, and add --max-delete=100 as a safety valve on scheduled jobs.A backup that survives being needed
- Three copies, two media, one off-site — and at least one copy the compromised host cannot write to (append-only or object-lock storage).
- Encrypt off-site copies, because copied data leaves the boundary of your disks.
- Record the restore procedure next to the backup, not in someone's memory.
- Back up the configuration that makes the data usable: the compose file, the database dump command, the fstab entries.
- Alert on the job failing. A silent backup schedule is worse than none, because you believe it ran.
# a database needs a logical dump, not a copy of live files
docker exec db pg_dump -U app app | gzip > /backups/app-$(date +%F).sql.gz
docker exec db pg_dumpall -U postgres | zstd > /backups/cluster-$(date +%F).sql.zst
# volume data as a tar stream through a helper container
docker run --rm -v pgdata:/data -v /backups:/backup alpine \
tar -czf /backup/pgdata-$(date +%F).tar.gz -C /data .
# restic to object storage, with retention
restic -r s3:s3.example.com/backups backup /srv /etc
restic -r s3:s3.example.com/backups forget --keep-daily 7 --keep-weekly 4 --prune
restic -r s3:s3.example.com/backups restore latest --target /tmp/restore
# and then actually check it
ls -l /tmp/restore/srv/app/config.yml
gzip -t /backups/app-2026-09-18.sql.gz && echo archive intact| Approach | RPO | Cost | Note |
|---|---|---|---|
rsync --link-dest | Hours | Low disk | Hard-linked snapshots on any filesystem |
| restic / borg | Minutes | Deduplicated | Encrypted, incremental, verifiable |
| Filesystem snapshot | Seconds | Storage-side | Same disk; pair with a copy elsewhere |
| Database logical dump | Hours | Small | Portable and consistent across versions |
| Streaming replication | Near zero | A second server | High availability, not a substitute for a backup |
FAQ
Does the root's crontab backup run include the right files?
Use absolute paths, set
PATH explicitly, and have the job write a status line to a log you monitor. A cron job inheriting a different environment is the most common reason a backup silently produces an empty archive.Why is my incremental backup as large as the first one?
Check the semantics rather than the flags:
--link-dest needs an existing previous snapshot at the exact relative path, and a changed mtime or permission bit makes rsync treat an otherwise identical file as different. -a with --delete and no --checksum trusts size and time.Related
Disks, filesystems and mounts Scheduling work: cron and systemd timers
Last refreshed 2026-09-18.