rsync жорсткі посилання – робоча схема для інкрементальних бекапів Linux-сервера без важкого backup-стека. На виході – окремі каталоги зі станом файлів на різні дати, але без дублювання незмінних даних. Для homelab це один із найпростіших способів отримати snapshot-подібні копії, які легко перевіряти і так само легко відновлювати.
Суть підходу проста: перший запуск копіює все, наступні – лише зміни. Файли, які не змінювалися, в новому каталозі бекапу не копіюються заново, а прив’язуються через hard links до попереднього snapshot. Ззовні кожен backup виглядає як повна копія.
Такий варіант добре закриває файлові бекапи конфігів, каталогів сервісів, web-root, домашніх директорій, невеликих volume для self-hosted застосунків. А ось для гарячих баз даних без dump це вже погана ідея – отримаєте красиву, але логічно биту копію.
Що саме будуємо
Потрібна схема з трьох частин: директорія для snapshot, симлінк або службовий запис на попередній backup, і скрипт, який запускає rsync з опцією –link-dest. Саме вона робить rsync жорсткі посилання практичним інструментом для щоденних інкрементальних бекапів.
Структура на backup-сховищі зазвичай виглядає так:
/backup/server01/ ├── 2026-05-05_010000/ ├── 2026-05-06_010000/ ├── 2026-05-07_010000/ └── latest -> 2026-05-07_010000
Кожен каталог містить повне дерево файлів. Різниця лише в тому, що незмінні файли мають ті самі inode, якщо подивитися через
ls -li
на кілька snapshot. Дисковий простір росте тільки разом зі змінами.
Налаштування rsync + hard links
Нижче – базовий сценарій для локального backup-диска, змонтованого в
/backup
. Якщо бекап іде на інший сервер, команда лишається тією самою по суті, тільки шлях призначення буде через SSH.
-
Створити каталог для бекапів.
sudo mkdir -p /backup/server01
-
Підготувати список виключень. Не треба тягнути псевдофайлові системи, кеші і тимчасові каталоги.
sudo tee /root/backup-exclude.txt > /dev/null <<'EOF' /proc /sys /dev /run /tmp /mnt /media /lost+found /swapfile /backup EOF
-
Створити скрипт запуску.
sudo tee /usr/local/sbin/rsync-snapshot-backup.sh > /dev/null <<'EOF' #!/bin/bash set -euo pipefail BACKUP_ROOT="/backup/server01" STAMP="$(date +%F_%H%M%S)" DEST="$BACKUP_ROOT/$STAMP" LATEST="$BACKUP_ROOT/latest" EXCLUDES="/root/backup-exclude.txt" mkdir -p "$DEST" if [ -L "$LATEST" ] || [ -d "$LATEST" ]; then LINK_DEST="--link-dest=$LATEST" else LINK_DEST="" fi rsync -aHAX --delete $LINK_DEST --numeric-ids --info=stats2,progress2 --exclude-from="$EXCLUDES" / "$DEST" ln -sfn "$DEST" "$LATEST" EOF sudo chmod +x /usr/local/sbin/rsync-snapshot-backup.sh -
Запустити вручну перший backup.
sudo /usr/local/sbin/rsync-snapshot-backup.sh
Опції тут не випадкові.
-a
зберігає базові атрибути,
-H
важлива, якщо в джерелі вже є hard links,
-A
і
-X
потрібні для ACL та extended attributes.
--numeric-ids
рятує від сюрпризів, коли UID/GID на системах не збігаються.
Найпоширеніша помилка в цьому місці – запускати backup всередину каталогу, який сам теж потрапляє в копіювання. Петля виходить швидко, а місце закінчується ще швидше.
Якщо сховище на іншому сервері
Для віддаленої машини підхід той самий: rsync копіює дерево через SSH, а –link-dest посилається на попередній snapshot уже на цільовому боці. Це нормальний варіант для другого вузла в стійці, NAS або окремого backup-host у домашній мережі.
rsync -aHAX --delete
--numeric-ids
--exclude-from="/root/backup-exclude.txt"
--link-dest=/srv/backup/server01/latest
-e ssh
/ backup@backup-host:/srv/backup/server01/2026-05-05_010000/
Тут є тонкий момент. Шлях у
--link-dest
має бути видимим для цільової системи, а не для джерела. Через це багато хто отримує повні копії замість інкрементальних і помічає проблему лише тоді, коли диск уже забитий.
Ще один практичний момент: для великої кількості дрібних файлів вузьким місцем часто стає не мережа, а metadata I/O на backup-диску. На повільних USB-накопичувачах ця схема працює, але не швидко.
Як перевірити, що hard links справді працюють
Перевірка проста: два однакові файли з різних snapshot мають однаковий inode і link count більше одиниці. Якщо inode різні, rsync жорсткі посилання не спрацювали, і кожен backup займає повний обсяг.
Спочатку подивитися на лічильник посилань:
ls -li /backup/server01/2026-05-05_010000/etc/hosts ls -li /backup/server01/2026-05-06_010000/etc/hosts
Або перевірити через stat:
stat /backup/server01/2026-05-05_010000/etc/hosts stat /backup/server01/2026-05-06_010000/etc/hosts
Після цього корисно звірити споживання місця:
du -sh /backup/server01/* df -h /backup
У нормальному сценарії новий snapshot виглядає як повний, але реальний приріст місця відповідає лише зміненим файлам. Якщо після кожного запуску обсяг стрибає майже на розмір усієї системи, проблема майже завжди в неправильному
--link-dest
, змінених правах, іншій файловій системі або постійно змінюваних даних на кшталт баз, кешів і журналів.
Де ця схема ламається найчастіше
Головна пастка – hard links працюють тільки в межах однієї файлової системи. Якщо snapshot лежать на різних mount point, економії не буде. Формально backup завершиться, але всі файли запишуться як нові.
Друга проблема – live-дані. Каталог PostgreSQL, MariaDB або SQLite, який копіюється під час запису, легко дає неконсистентний результат. Для баз нормальний шлях інший: dump, snapshot файлової системи або нативний backup-механізм.
Під навантаженням часто спливають дрібниці: змінений ownership після відновлення тестової копії, втрачений xattr, відсутній ACL, зайві bind mount у дереві backup. Саме тому опції
-HAX
і список excludes тут не косметика.
Є ще межа масштабу. Для одного-двох серверів, конфігів і файлових сервісів схема відпрацьовує добре. Коли snapshot тисячі, а inode на backup-томі тануть швидше за гігабайти, доведеться або жорстко чистити ротацію, або переходити на інструменти іншого класу – наприклад rsync як транспорт уже не витягує весь сценарій сам по собі.
Ротація бекапів без зайвої магії
Для homelab зазвичай вистачає простої схеми: кілька щоденних, кілька тижневих, кілька місячних snapshot. Видалення старих каталогів безпечне – файл зникне фізично лише тоді, коли на нього не лишиться інших hard links.
find /backup/server01 -maxdepth 1 -mindepth 1 -type d -mtime +14 -exec rm -rf {} ;
З цим треба акуратно. Команда вище підходить тільки якщо назви каталогів, політика зберігання і структура вже стабільні. Для серйознішої ротації краще окремий скрипт з перевіркою імен, щоб не видалити не той mount або не ту директорію.
Автоматичний запуск
Найпростіше рішення – cron. Для домашнього сервера цього достатньо, якщо backup-диск змонтований постійно і скрипт уже перевірений вручну.
sudo crontab -e
0 1 * * * /usr/local/sbin/rsync-snapshot-backup.sh >> /var/log/rsync-snapshot-backup.log 2>&1
Краще спочатку дати цьому покрутитися кілька днів і подивитися на лог, inode, реальний приріст місця. Саме на цьому етапі вилазять каталоги, які не варто було копіювати, і сервіси, для яких потрібен окремий pre-backup hook.
Відновлення файлів і каталогів
Відновлення тут прямолінійне: потрібний snapshot уже містить повне дерево. Можна копіювати окремий файл, каталог або синхронізувати назад у систему тим самим rsync.
sudo rsync -aHAX /backup/server01/2026-05-07_010000/etc/nginx/ /etc/nginx/
Для швидкого rollback конфігів це сильна сторона підходу. Не треба піднімати backup-сервер, індекс або спеціальний клієнт. Але рівно до того моменту, поки не з’являються дедуплікація між хостами, шифрування, політики retention і перевірка цілісності на рівні системи backup.