HomeLab.net.ua

Твоє залізо. Твої правила. Твоя мова.

Як налаштувати rsync + hard links для інкрементальних бекапів Linux-сервера

Опубліковано

rsync жорсткі посилання

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.

  1. Створити каталог для бекапів.

    sudo mkdir -p /backup/server01
  2. Підготувати список виключень. Не треба тягнути псевдофайлові системи, кеші і тимчасові каталоги.

    sudo tee /root/backup-exclude.txt > /dev/null <<'EOF'
    /proc
    /sys
    /dev
    /run
    /tmp
    /mnt
    /media
    /lost+found
    /swapfile
    /backup
    EOF
  3. Створити скрипт запуску.

    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
  4. Запустити вручну перший 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.