Каждый, кто держал сервер хотя бы год, знает: копий много не бывает, а места под них всегда мало. BorgBackup отвечает на эту проблему дедупликацией на уровне содержимого: повторяющиеся куски данных хранятся один раз, при этом каждый архив выглядит полноценным слепком. Проект родился в 2015 году как форк Attic и с тех пор стал одним из двух главных инструментов своего класса наряду с restic. Borg силён там, где хранилище своё: локальный диск, резервная машина в соседней стойке или свой сервер в другом дата-центре через SSH. Ниже разобран полный цикл: установка, ключи, шифрованный репозиторий, архивы с компрессией, автоматизация, ротация, проверка целостности и восстановление с разбором ловушек.

Почему дедупликация Borg экономит терабайты на повторных копиях

Секрет экономии в нарезке по содержимому. Borg режет поток данных на куски переменной длины, ориентируясь на сами байты, а не на позиции в файле: добавление строки в начало документа сдвигает только соседние куски, остальной массив остаётся узнанным. Каждый кусок хранится один раз, а архивы ссылаются на общие блоки по хешам. На практике это выглядит так: первый полный архив сайта весит честный гигабайт, недельная цепочка ежедневных снимков добавляет к нему десятки мегабайтов, а не ещё семь гигабайтов. Десять снапшотов одного сервера занимают ненамного больше одного. Для команды из трёх человек с десятью сайтами экономия выливается в осязаемую сумму: репозиторий, который без дедупликации занял бы терабайт, помещается в сотню гигабайтов, и диск резервного сервера не приходится расширять каждый год.

Компрессия работает поверх дедупликации и выбирается флагом: zstd как разумный баланс скорости и степени сжатия, lz4 для быстрых прогонов на слабом железе, zlib для упора в экономию. Текст, конфигурации и логи сжимаются в разы, медиафайлы почти не сжимаются, но и не мешают остальным.

Шифрование закрывает третью заботу. Режим repokey хранит ключ внутри репозитория, защищённый паролем, и репозиторий переносится на другую машину без лишних файлов. Режим keyfile держит ключ только на клиенте, и утрата клиента означает утрату доступа. В обоих случаях на диске лежит шум: без пароля не читается ни структура, ни имена, ни содержимое. Актуальная стабильная ветка 1.4.x, а вторая мажорная версия пока выходит бета-сборками и репозитории первой не читает.

Отдельное слово об архитектуре: репозиторий один, архивов в нём много, и каждый архив это полноценный слепок на свою дату. Это упрощает мышление: вместо цепочек инкрементов, где потеря звена рвёт историю, получается плоский список снимков, из которого восстановление берёт нужный целиком.

Установка Borg и ключи SSH для удалённого хранилища

Установка на боевом сервере занимает минуту, пакет есть в штатных репозиториях свежих выпусков Ubuntu и Debian:

apt update && apt install -y borgbackup
borg --version

Для удалённого репозитория есть тонкость: Borg обязан стоять на обеих сторонах. Локальный клиент нарезает, шифрует и дедуплицирует, а на приёмнике утилита только принимает поток и пишет его на диск. Если на резервном сервере Borg нет, команда упадёт с очевидной ошибкой, и это лучше выяснить до первого гигабайта. Заодно сверяются версии: большая разница в версиях на сторонах это источник странных ошибок, которого легко избежать.

Ключи SSH готовятся как для любого другого копирования:

ssh-keygen -t ed25519 -f /root/.ssh/borg_key -N ""
ssh-copy-id -i /root/.ssh/borg_key.pub Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
ssh -i /root/.ssh/borg_key Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. "borg --version"

Третья строка одновременно проверяет и вход без пароля, и наличие Borg на той стороне. Пользователь backup заводится без лишних привилегий, ему нужен только его каталог копий с правом записи.Отдельный совет про firewall: SSH между боевым и резервным сервером живёт по обычным правилам, порт 22 или нестандартный открывается только для пары адресов, и хранилище не требует ничего сверх привычного канала. Ключ при желании запирается одной разрешённой командой в authorized_keys, чтобы утёкший секрет не открывал оболочку на резервной машине, а сам каталог копий принадлежит именно этому пользователю.

Создание шифрованного репозитория локально и на удалённом сервере

Репозиторий создаётся одной командой, а выбор режима шифрования это единственное решение, которое потом дорого менять:

borg init --encryption=repokey-blake2 /var/backups/server-repo
borg init --encryption=repokey-blake2 Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.:/backups/server-repo

Первая строка строит хранилище на своём диске, вторая на удалённом сервере, синтаксис различается только адресом. Команда спросит пароль репозитория, и к его хранению применяются те же правила, что и в любом шифровании: пароль это ключ ко всем копиям, его резервная копия лежит отдельно от сервера, утрата означает бесполезные терабайты.

Для скриптов пароль не хранят в открытом виде в команде: используются переменные окружения. Переменная BORG_PASSPHRASE принимает пароль напрямую, BORG_PASSCOMMAND указывает команду, которая его печатает, например cat на файл с правами 600. Второй способ чище: файл читается один раз за прогон и не светится в списке процессов.

Адрес репозитория и ключ SSH тоже выносятся в переменные, после чего все команды сокращаются до самих себя без префиксов:

export BORG_REPO=Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.:/backups/server-repo
export BORG_RSH="ssh -i /root/.ssh/borg_key"
export BORG_PASSCOMMAND="cat /root/.borg-pass"

Такой блок удобно держать отдельным файлом с правами 600 и подключать строкой source из скриптов: сам сценарий остаётся чистым, а переезд на другой сервер сводится к правке трёх строк.

Первый архив с компрессией zstd и шаблонами исключений

Архив создаётся командой create, и в её опциях нет лишних:

borg create --stats --compression zstd ::site-{now} /var/www /etc

Шаблон {now} подставляет дату и время в имя архива автоматически, это чище ручной подстановки и не даст двум прогонам перезаписать друг друга. В создании архива пригодятся следующие опции:

  1. --stats, сводка объёма, скорости и доли новых данных в конце;
  2. --progress, индикатор выполнения для ручных прогонов;
  3. --exclude, маски исключений для кэшей, логов и временных файлов;
  4. --one-file-system, запрет выхода за пределы текущей файловой системы;
  5. --checkpoint-interval, контрольные точки на длинных архивах для докрутки после сбоя.

Первый прогон честно показывает, зачем всё затевалось: сводка --stats сообщает полный объём и скорость. Второй прогон тем же списком путей укладывается в минуты и добавляет проценты, потому что повторяющиеся куски уже лежат в репозитории. Исключения экономят и время, и место: тяжёлые кэши, меняющиеся каждый прогон, портят статистику дедупликации, и одну маску --exclude полезнее написать сразу, чем месяц смотреть на разросшийся репозиторий. Базы данных перед архивом выгружаются дампом в staging-каталог, живую базу копировать нельзя, связка привычная: дамп, архив Borg со staging внутри, очистка старых дампов.

Автоматизация через скрипт с расписанием и логом

Ручные архивы не живут дольше месяца, сценарий автоматизации пишется один раз и годами не требует внимания. Типовой скрипт для сервера с сайтом выглядит так:

#!/bin/bash
set -euo pipefail

source /root/.borg-env

pg_dump --dbname=site_db --file=/var/backups/staging/site_db.sql

borg create --stats --compression zstd ::site-{now} /etc /var/www /var/backups/staging --exclude /var/www/*/cache

borg prune --glob-archives 'site-*' --keep-daily 7 --keep-weekly 4 --keep-monthly 6

borg compact

borg check --repository-only

echo "$(date) borg архив собран" >> /var/log/borg-backup.log

Первая строка подключает файл с переменными окружения, описанный выше, дальше идёт дамп базы, затем архив с фильтром кэшей, ротация с шаблоном имени, уплотнение освободившегося места и быстрая проверка. Шаблон --glob-archives в ротации защищает чужие архивы: правило действует только на снапшоты, чьи имена начинаются с site-, и не тронет архивные полки с другими префиксами. Строка set -euo pipefail останавливает сценарий на первой ошибке, чтобы тишина в журнале не выдавалась за порядок.

Скрипт повязывается на расписание одной строкой в crontab:

40 2 * * * /root/borg-backup.sh

Уведомление о результате делается любой доступной доставкой: строка curl на адрес вебхука в конце скрипта сообщит об успехе или провале, а файл журнала сохранит историю за месяц. Раз в месяц вместо ночной проверки запускается полная с чтением данных, и этот прогон ставится на выходное окно, потому что чтение сотен гигабайт занимает время.

Ротация политикой prune и проверка целостности репозитория

Хранение строится политикой prune, и типовая лестница выглядит знакомо: семь ежедневных, четыре недельных, шесть месячных, годовая полка для глубоких проверок. Команда компактна и читается как документ:

borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6

После prune место освобождает отдельная команда compact: забытые архивы исчезают из списка сразу, а физическое высвобождение чанков происходит именно на этом шаге, поэтому пара prune и compact всегда ходит вместе. Список архивов с датами и размерами служит индикатором здоровья: цепочка должна расти ровно, без пропусков и без подозрительных скачков объёма, а внезапно распухший архив это повод пересмотреть исключения.

Проверка целостности дважды в ритуале: раз в неделю быстрая проверка репозитория, раз в месяц полный прогон с чтением данных:

borg check --repository-only

Зависшие после прерывания блокировки снимает команда break-lock, но прежде чем её запускать, стоит убедиться, что параллельных прогонов действительно нет: два процесса одновременно пишут в один репозиторий без блокировок плохо. Для справки по конкретному архиву есть borg info, она покажет дедуплицированный объём, компрессию и время создания. Сводка по репозиторию целиком показывает дедуплицированный объём против логического: коэффициент экономии виден сразу, а стабильно растущая разница между цифрами подсказывает, что исключения перестали поспевать за проектом.

Восстановление файлов из архива и типичные ловушки

Список архивов и файлов внутри них смотрится короткими командами:

borg list
borg list ::site-2026-09-28-0230

Извлечение имеет одну известную ловушку: borg extract всегда разворачивает файлы в текущий каталог. Команду запускают из пустой папки, созданной специально, иначе файлы лягут поверх боевых конфигураций, и разбирать смесь придётся руками:

mkdir -p /tmp/restore && cd /tmp/restore
borg extract ::site-2026-09-28-0230 etc/nginx

Для прогулок по архивам как по каталогам репозиторий монтируется в пустую папку через FUSE, и нужный файл просто копируется из нужной даты. Для выгрузки архива целиком в обычный tarball служит export-tar, удобно, когда копию нужно отдать человеку без Borg. А команда diff покажет, что изменилось между двумя датами, и выручает при поиске момента, когда всё сломалось. Восстановление одиночного файла через монтирование укладывается в минуту, полный архив разворачивается со скоростью диска, и время репетиции известно заранее, потому что измерялось хотя бы однажды.

Ловушки заканчиваются историями про пароль: репозиторий repokey переносится, но без пароля не читается, и это не дефект, а суть шифрования. Версии Borg на всех машинах, работающих с одним репозиторием, держат одинаковыми: старая утилита не знает новых форматов. А переход на вторую мажорную версию потребует нового репозитория, старые она не читает, поэтому линию 1.4 в продакшене держат до выхода стабильной двойки.

Копия проверяется репетицией восстановления раз в месяц, это правило не зависит от инструмента. Borg делает репетицию короткой: mount, diff и extract укладываются в минуты, а репозиторий в соседнем здании превращает выход из строя основного сервера в неприятность на вечер. Скучная и многократно проверенная схема это и есть хорошая схема.