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

Резервное копирование VPS за шесть шагов: от незащищённого сервера до проверенного зашифрованного репозитория на второй машине во второй юрисдикции — сравнение restic и Borg, дампы баз данных, которые восстанавливаются без ошибок, таймер systemd и append-only репозиторий, который взломанный сервер не сможет стереть. Вторая машина стоит $3.90 в месяц. Протестировано на Debian 12.
Инвентаризация
Что нельзя восстановить заново
Установить
restic или Borg
Приёмник
Второй бастион
Инициализация
Репозиторий + первый запуск
Автоматизация
Дампы, таймер, хранение
Тренировка
Восстановление с замером времени
Почти любой спор о резервном копировании идёт не туда уже в первую минуту, потому что у собеседников в голове разные сбои. Один представляет себе умерший диск. Другой — утро вторника, когда аккаунт просто исчез. Им нужны разные ответы, и схема, переживающая только первый сценарий, кажется надёжной ровно до тех пор, пока её не проверит второй.
Есть четыре сценария сбоя, от которых стоит защищаться заранее, и это не вариации одной темы — каждый преодолевает свою собственную защиту.
Оборудование. Отказывает NVMe, умирает хост-нода, массив теряет сразу два элемента. Это тот сбой, к которому все готовятся заранее, и на современной виртуализированной инфраструктуре он встречается реже всего. Избыточное хранилище с ним справляется. Больше ни с чем оно не справляется — именно поэтому RAID не является резервной копией и никогда ей не был: он добросовестно копирует каждую запись, включая ту, что означает «удалить всё».
Человек. Скрипт миграции, запущенный на продакшене. DROP TABLE не в том терминале. rsync с перепутанными местами аргументами. Это, с большим отрывом, самая частая причина реальной потери данных, и единственная защита от неё — история: копия, сделанная до ошибки и хранящаяся там, куда ошибка не дотянулась. Ключевой ингредиент — время: если единственная копия — это зеркало с отставанием в тридцать секунд, оно уже содержит эту ошибку.
Злоумышленник. Кто-то получает root, либо программа-вымогатель проникает через непропатченное приложение. Первое, что делает современный вымогатель, — ищет конфигурацию резервного копирования: учётные данные, примонтированные шары, облачные ключи — и уничтожает всё найденное, именно потому что работающее восстановление превращает катастрофу в дело одного дня. Учётные данные для бэкапа, которые могут удалять, — это учётные данные, которые наследует атакующий. Именно для этого сбоя в руководстве существует глава про append-only.
Провайдер. Ничего не сломалось и никто не атаковал — вы просто потеряли доступ. Аккаунт заблокирован по автоматическому сигналу о мошенничестве, карта не прошла, пока вы были в поездке, спор по платежу, проверка на соответствие требованиям, судебный приказ, вручённый провайдеру. Машина цела и недоступна, а вместе с ней недоступен и каждый снапшот внутри этого аккаунта. На этот сбой не отвечает никакая избыточность внутри одного провайдера, и именно поэтому серьёзная резервная копия живёт под другой крышей.
| Защита | Оборудование | Человеческая ошибка | Программы-вымогатели | Утраченный аккаунт |
|---|---|---|---|---|
| RAID / избыточное хранилище | Да | Нет | Нет | Нет |
| Снапшот провайдера, тот же аккаунт | Да | Частично | Нет | Нет |
| Репозиторий вне сервера, ключ с правом записи | Да | Да | Нет | Да |
| Репозиторий вне сервера, append-only, другая юрисдикция | Да | Да | Да | Да |
Последняя строка — это то, вокруг чего строится остальная часть руководства. Она не дороже строки выше: настройка append-only бесплатна, а вторая юрисдикция стоит те же $3.90, что и первая.
Правило 3-2-1, переформулированное для одного VPS. Три копии данных, на двух разных типах хранилищ, одна из которых — вне площадки. Классическая формулировка родом из эпохи ленточных накопителей и офисных серверов, и дух правила переживает перенос лучше, чем буква. Для одной самостоятельно размещённой машины это звучит так: рабочие данные на VPS, репозиторий на второй машине где-то ещё и — для всего по-настоящему незаменимого — третья копия, которую можно подержать в руках: на диске дома или в ящике стола. По-настоящему важно не число «три». Важно, сколько независимых вещей должно пойти не так, прежде чем данные пропадут. Минимум, который стоит иметь, — два.
Категория, которая вам нужна, называется дедуплицирующий шифрующий инструмент для снапшотов. Он разбивает каждый файл на чанки, определяемые по содержимому, шифрует каждый чанк на машине-владельце данных и сохраняет его один раз, сколько бы снапшотов на него ни ссылалось. Это даёт сразу три свойства: множество точек восстановления почти за цену одной, открытый текст, который никогда не покидает источник, и передачу, при которой перемещается только изменившееся.
В этой нише доминируют две программы, и обе превосходны. restic — это единый статический бинарник на Go с широким выбором бэкендов хранения. BorgBackup — программа на Python, которая общается по SSH со своей же копией на удалённой стороне. Всё, что ниже, — честная разница между ними.
| Свойство | restic | BorgBackup | rsync / rclone |
|---|---|---|---|
| Шифрование на стороне клиента | Всегда включено, не опционально | Всегда включено, не опционально | Только через rclone crypt remote |
| Дедупликация | Чанки, определяемые по содержимому | Чанки, определяемые по содержимому | Ничего, либо деревья из жёстких ссылок |
| История снапшотов | Да, с политиками хранения | Да, с политиками хранения | Зеркало текущего момента |
| Требования к приёмнику | Ничего — SFTP, S3, REST, rclone | Borg установлен на обоих концах | SSH-аккаунт или API |
| Принудительный append-only | Через rest-server или object lock | Встроено, поверх обычного SSH | Нет |
| Подходит для | Почти всем, большинство бэкендов | Своя SSH-машина, append-only | Зеркалирование, а не резервное копирование |
rsync присутствует в таблице потому, что это первое, за что берётся большинство, и потому, что это по-настоящему неправильный инструмент: зеркало добросовестно воспроизводит и удаление, а зеркало диска, зашифрованного «на месте», само по себе нигде не зашифровано. Своё место внутри бэкапа он заслуживает как средство перемещения готового архива, а не как сам архив.
В этом руководстве для каждой отличающейся команды показаны и restic, и Borg, так что следовать ему можно с любым из них. Там, где нужен единственный выбор — разобранный пример, таймер, скрипт — используется restic, потому что тогда машине назначения нужен лишь SSH-аккаунт, а это оставляет вторую машину максимально скучной.
Резервировать всю файловую систему целиком — решение обоснованное, но расточительное. Большую часть сервера можно воссоздать одной командой пакетного менеджера, а каждый лишний гигабайт в репозитории замедляет prune, повышает стоимость хранения и замедляет поиск, когда вы торопитесь. Полезное упражнение — обратное: перечислить то, что чистая установка вам не вернёт.
Для типичного самостоятельно размещённого VPS этот список короткий и всегда содержит эти пять пунктов:
Превратите список в два файла на исходном сервере. Include-список делает намерение видимым; exclude-список отсеивает шум. Оба читаются командой бэкапа, и оба сами должны входить в резервную копию.
/etc/backup/include.txt
/etc
/opt
/root
/var/backups
/var/lib/docker/volumes
/home
/etc/backup/exclude.txt
# Caches and rebuildable artefacts
**/node_modules
**/.cache
**/*.tmp
/var/lib/docker/volumes/*/_data/cache
# Log noise — keep the config, drop the volume
/var/log/journal
# Never back up the mount point you restore into
/mnt/restore
Два замечания по этой строке про Docker, потому что на ней часто ошибаются. Именованные тома (named volumes) лежат в /var/lib/docker/volumes — это как раз то, что нужно; bind mount'ы лежат там, куда вы их поместили, обычно рядом с файлом Compose, и охватываются /opt. Образы контейнеров — не данные: они возвращаются через pull, и бэкап, который их тащит, тащит лишние ненужные гигабайты.
Наконец, прежде чем что-либо строить вокруг этого, измерьте результат. Это число подскажет, какой тариф репозитория заказывать и займёт ли первый запуск четыре минуты или сорок:
du -sh --exclude=/var/lib/docker/overlay2 /etc /opt /root /home /var/lib/docker/volumes
Установите инструмент на машину, которой принадлежат данные. В Debian и Ubuntu оба пакета есть в репозиториях, но для restic пакет дистрибутива часто отстаёт на релиз-другой — а это важно, потому что возможности репозитория и оптимизации производительности приходят именно в точечных релизах. Установите пакет, а затем дайте restic обновиться самостоятельно:
apt update && apt install -y restic
restic self-update
restic version
Для Borg правильный выбор — пакет из дистрибутива, потому что версия на источнике и версия на приёмнике должны понимать друг друга, а согласованная пара из одного релиза дистрибутива — наименее болезненный способ это обеспечить:
apt install -y borgbackup
borg --version
Пара слов о мажорных версиях, прежде чем доверять репозиторию годы истории: формат репозитория Borg менялся между мажорными линиями, и перенос репозитория через эту границу — это конвертация, а не обновление. Читайте release notes версии, которую устанавливаете, и держите приёмник на той же мажорной линии, что и источник. restic сохраняет обратную совместимость между релизами и добавляет новые возможности за явной версией репозитория, поэтому там аналогичное беспокойство меньше.
Ни одному из инструментов не нужен демон, агент или открытый порт на источнике. Это стоит сказать прямо: устанавливаемая вами система резервного копирования не добавляет защищаемой машине ни слушающего сервиса, ни новой поверхности атаки. Всё, что она делает, — исходящие соединения по расписанию, по SSH.
У хоста репозитория одна задача и почти никаких требований. Ему не нужны ядра процессора, потому что он никогда не читает ваши данные — он хранит зашифрованные blob'ы, которые не может открыть. Ему не нужна память, потому что работа по дедупликации происходит на источнике. Ему нужен диск, стабильный адрес и расположение, куда не дотягиваются проблемы исходной машины.
В панели: Order → VPS → Sentinel, образ Debian 12 и — в этом вся суть — другой бастион, отличный от того, где работает источник. Helsinki — рабочая машина, Reykjavík — хранилище. Для открытия аккаунта не требуется email, а счёт оплачивается в Monero, Bitcoin, Lightning или любом другом поддерживаемом активе, а значит вторая машина не возвращает ту личность, которую первая так тщательно скрывала.
| Целевой репозиторий | Ежемесячно | Хранилище | Хранимый рабочий набор | Стоимость восстановления |
|---|---|---|---|---|
| Sentinel | $3.90 | 120 GB NVMe | ~30 ГБ | Ничего — безлимитный трафик |
| Garrison | $7.90 | 240 GB NVMe | ~70 ГБ | Ничего — безлимитный трафик |
| Ravelin | $16.90 | 480 GB NVMe | ~150 ГБ | Ничего — безлимитный трафик |
| Bulwark | $32.90 | 960 GB NVMe | ~300 ГБ | Ничего — безлимитный трафик |
| Объектное хранилище класса S3 | ~$0.023/ГБ | Эластичное | Любой | Тарифицируемый исходящий трафик |
Столбец «рабочий набор» предполагает ежедневные снапшоты, хранящиеся год, при обычной интенсивности изменений; репозиторий, в котором преобладают медленно меняющиеся файлы, расходует место гораздо экономнее, а репозиторий с крупными перезаписываемыми бинарниками — гораздо быстрее. Последняя строка здесь для масштаба и ради детали, о которой все забывают до самого плохого дня: объектное хранилище берёт плату и за скачивание, а скачивание — это как раз то, что вы делаете в экстренной ситуации.
На приёмнике — непривилегированный пользователь и каталог.
adduser --disabled-password --gecos "" backup
install -d -m 0700 -o backup -g backup /srv/restic/web01
У пользователя backup нет пароля, нет sudo и некуда входить. Он существует только для того, чтобы владеть каталогом. Если вы резервируете несколько машин, выделите каждой отдельный каталог — один репозиторий на источник не даёт компрометации одной машины затронуть историю другой.
На источнике — ключ, который не используется больше ни для чего.
ssh-keygen -t ed25519 -f /root/.ssh/id_backup -N "" -C "backup:web01"
cat /root/.ssh/id_backup.pub
Не используйте повторно свой ключ для входа. Этот ключ хранится на диске в незашифрованном виде, потому что его должен использовать автономный таймер в три часа ночи, — и это как раз тот случай, когда ключ должен быть ограничен одним приёмником и одной задачей.
Снова на приёмнике — ограничьте возможности этого ключа. Вставьте публичный ключ в /home/backup/.ssh/authorized_keys с префиксом перед ним. Для restic через SFTP:
restrict,from="198.51.100.7" ssh-ed25519 AAAAC3NzaC1lZDI1... backup:web01
Опция restrict одним словом отключает проброс портов, проброс агента, X11 и выделение PTY, оставляя только передачу файлов и ничего больше. Условие from= привязывает ключ к адресу источника, так что копия, украденная с сервера, бесполезна где-либо ещё. Для Borg сделайте ещё один шаг и принудительно задайте команду — именно там живёт его режим append-only:
command="borg serve --append-only --restrict-to-path /srv/borg/web01",restrict ssh-ed25519 AAAAC3NzaC1lZDI1... borg:web01
Эта строка — вся защита от вымогателей в одном месте: что бы ни отправлял источник, приёмник будет запускать только borg serve, только внутри этого пути и только в режиме добавления. Root-шелл на источнике не сможет использовать этот ключ, чтобы стереть данные за прошлый месяц.
Укажите приёмник один раз — на источнике. Пропишите это в /root/.ssh/config, чтобы все последующие команды были короткими, а адрес хранился ровно в одном файле:
Host rkv-repo
HostName 198.51.100.42
User backup
IdentityFile /root/.ssh/id_backup
IdentitiesOnly yes
Затем один раз подключитесь вручную — ssh rkv-repo — чтобы принять ключ хоста. Автоматический таймер не сможет ответить на запрос подтверждения отпечатка, а первый бэкап, молча зависший на неделю, — классика жанра. Пока вы на машине назначения, отнеситесь к ней так же, как к любой другой своей машине: пройдите по ней чек-лист защиты в первый час. Она хранит копию всего, и она заслуживает полного часа.
Сгенерируйте кодовую фразу, которую вы никогда не будете вводить вручную. Скрипт читает её из файла, так что длина ничего не стоит и нет причин делать её запоминающейся:
openssl rand -base64 32 > /root/.restic-pass
chmod 600 /root/.restic-pass
cat /root/.restic-pass
Теперь остановитесь и скопируйте эту строку куда-нибудь за пределы этого сервера. Менеджер паролей, бумажная записка в ящике стола, второй компьютер — куда угодно, что переживёт потерю источника. Это тот же самый сбой, что уничтожает самостоятельно размещённые хранилища паролей и движки автоматизации: ключ, расшифровывающий всё, хранится рядом со всем остальным, и оба теряются вместе. Репозиторий, кодовая фраза которого существовала только на защищаемой им машине, — это не резервная копия, а очень аккуратная куча шума.
Создайте файл окружения, который будут читать все последующие команды:
/etc/backup/restic.env
RESTIC_REPOSITORY=sftp:rkv-repo:/srv/restic/web01
RESTIC_PASSWORD_FILE=/root/.restic-pass
chmod 600 /etc/backup/restic.env
set -a; . /etc/backup/restic.env; set +a
restic init
Эквивалентная пара для Borg — режим repokey хранит ключ шифрования внутри репозитория, защищённый кодовой фразой, что удобно и именно поэтому нужно дополнительно экспортировать его копию:
export BORG_REPO=ssh://borg@rkv-repo/srv/borg/web01
export BORG_PASSCOMMAND="cat /root/.restic-pass"
borg init --encryption=repokey-blake2
borg key export :: /root/borg-key.txt # copy this off the server too
Первый снапшот. Запустите его вручную, понаблюдайте и дайте ему завершиться, прежде чем что-либо автоматизировать. Это медленный запуск — каждый чанк новый — и именно он покажет, правильно ли составлен include-список:
restic backup \
--files-from /etc/backup/include.txt \
--exclude-file /etc/backup/exclude.txt \
--tag nightly --one-file-system --verbose
Если источник обслуживает трафик, а выгрузка забивает канал, ограничьте её. Флаг принимает кибибайты в секунду, так что 20000 — это примерно 20 МБ/с:
restic backup --limit-upload 20000 --files-from /etc/backup/include.txt
Когда всё завершится, посмотрите, что вы на самом деле получили. Вот три команды, которые стоит запустить сейчас — и снова через полгода, когда вы уже перестанете об этом думать:
restic snapshots # the list, with dates and tags
restic stats latest # what the newest snapshot contains
restic ls latest /etc/backup # confirm a path you expected is in there
Стоит на секунду задержаться на третьей команде. Половина «сломанных» резервных копий на самом деле вовсе не сломаны — это полноценные, исправные репозитории не того каталога. Проверка ожидаемого пути прямо на первом снапшоте выявляет это, пока include-файл ещё свеж в памяти.
Правило для баз данных, сформулированное один раз. Работающий движок базы данных держит состояние в памяти и сбрасывает его на диск в своё время. Если читать его файлы в этот момент, вы получите набор страниц из нескольких разных моментов времени — файл, который может восстановиться, может восстановиться повреждённым, а может открыться без ошибок и тихо потерять последний час. Со стороны отличить одно от другого невозможно. Поэтому резервная копия никогда не трогает рабочие файлы напрямую: сначала на диск записывается дамп, и именно его забирает резервная копия.
Всё собирается в один скрипт. Поместите его в /usr/local/sbin/nb-backup.sh, сделайте исполняемым и держите его копию внутри самой резервной копии:
#!/bin/sh
set -eu
# ---- 1. Consistent dumps, written where the include list will find them.
install -d -m 0700 /var/backups/db
# PostgreSQL in a container:
docker exec -t app-db pg_dumpall -U postgres | gzip -9 > /var/backups/db/postgres.sql.gz
# MariaDB / MySQL, InnoDB tables:
# docker exec -t mail-db mariadb-dump --single-transaction --all-databases \
# | gzip -9 > /var/backups/db/mariadb.sql.gz
# SQLite — never a plain file copy:
# sqlite3 /opt/app/data/app.db ".backup '/var/backups/db/app.db'"
# ---- 2. The snapshot.
restic backup \
--files-from /etc/backup/include.txt \
--exclude-file /etc/backup/exclude.txt \
--tag nightly --one-file-system
# ---- 3. Retention, then reclaim the space it freed.
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
# ---- 4. Structural check every night; read 5% of the data on Sundays.
restic check
[ "$(date +%u)" = "7" ] && restic check --read-data-subset=5%
echo "backup ok: $(date -u +%FT%TZ)"
Три детали в этом скрипте делают больше, чем кажется на первый взгляд. set -eu в начале означает, что неудавшийся дамп прерывает запуск, а не тихо снапшотит вчерашний дамп на следующие полгода. forget и prune объединены в одну команду, потому что политика хранения, которая никогда не освобождает место, — это диск, который переполняется. А воскресная проверка читает часть реальных данных, а не только индекс — повреждение репозитория редко и происходит тихо, и обнаружить его может только чтение.
Таймер. Два небольших юнит-файла. Здесь правильный планировщик — systemd, а не cron, потому что таймер даёт Persistent: пропущенный из-за выключенной машины запуск происходит при следующей загрузке вместо того, чтобы молча потеряться, — а ещё потому, что journalctl тогда хранит историю каждого запуска.
/etc/systemd/system/nb-backup.service
[Unit]
Description=Nightly encrypted offsite backup
Wants=network-online.target
After=network-online.target docker.service
[Service]
Type=oneshot
EnvironmentFile=/etc/backup/restic.env
ExecStart=/usr/local/sbin/nb-backup.sh
Nice=10
IOSchedulingClass=idle
/etc/systemd/system/nb-backup.timer
[Unit]
Description=Nightly encrypted offsite backup
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=45m
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now nb-backup.timer
systemctl list-timers nb-backup.timer
systemctl start nb-backup.service && journalctl -u nb-backup.service -n 40 --no-pager
Случайная задержка — не украшение. Каждый самостоятельно размещённый сервер в интернете запускает обслуживание ровно в 03:00, и бэкап, стартующий в ту же секунду, что ротация логов, обновление сертификата и обновление контейнеров, всю ночь борется с ними за I/O. Размазывание старта на сорок пять минут ничего не стоит и убирает целый класс необъяснимо медленных ночей.
Затем сделайте сбой громким. Это тот шаг, который все пропускают, а именно он решает, был ли смысл во всём остальном. Резервное копирование, переставшее работать, ни о чём не сообщает: таймер продолжает срабатывать, сервис продолжает падать, а последний исправный снапшот всё дальше уходит в прошлое, пока на дашборде горит зелёный. Добавьте к сервису юнит OnFailure=, который присылает вам что-то, что вы действительно увидите, и раз в месяц заглядывайте в верх списка снапшотов. Одна строка, одна привычка.
restic snapshots --latest 3 # is the newest one from last night?
systemctl list-timers nb-backup.timer # is the timer still armed?
Есть две тренировки, и они отвечают на разные вопросы. Малая спрашивает: «данные действительно там и читаются?» — и занимает девяносто секунд. Большая спрашивает: «сколько на самом деле займёт вернуть сервис в строй?» — и занимает полдня, раз в год. Малую делайте ежемесячно. Большую — хотя бы раз, потому что ответ никогда не совпадает с тем, что предполагают.
Малая тренировка. Извлеките один каталог и один дамп во временный путь, затем сравните их с тем, что работает в проде:
install -d -m 0700 /mnt/restore
restic restore latest --target /mnt/restore --include /etc/backup
restic restore latest --target /mnt/restore --include /var/backups/db
diff -r /etc/backup /mnt/restore/etc/backup && echo "config identical"
gzip -t /mnt/restore/var/backups/db/postgres.sql.gz && echo "dump readable"
Ещё лучше, когда инструмент это позволяет: смонтировать репозиторий в режиме только для чтения и обойти его как обычную файловую систему. Каждый снапшот выглядит как каталог с датой, и историю можно листать через ls и cat, вообще ничего не восстанавливая. Для этого на машине, выполняющей монтирование, нужен FUSE:
restic mount /mnt/snapshots # then: ls /mnt/snapshots/snapshots/latest/
borg mount :: /mnt/snapshots # the Borg equivalent
Большая тренировка. Закажите временный VPS с почасовой оплатой и восстановите сервис на нём исключительно из репозитория — без заметок по памяти, без файлов, скопированных с рабочего сервера. Работайте по письменному runbook и исправляйте его по ходу дела, потому что найденные пробелы и есть смысл всего упражнения. Типичные находки, по частоте: кодовая фраза хранилась только на погибшей машине; записи DNS нигде не были зафиксированы; каталог, смонтированный через bind mount, оказался вне всех include-путей; для восстановления потребовалась версия пакета, которая больше не установлена по умолчанию; никто не знал, какой контейнер нужно запускать первым.
Замерьте время. Запишите цифру в начале runbook вместе с датой. Именно эта цифра — не размер репозитория, не количество снапшотов — единственная честная мера того, насколько сервис на самом деле защищён.
И иногда читайте эти данные. Ночная проверка проверяет только структуру: что индекс согласован сам с собой и ни один blob не отсутствует. Она не читает сами blob'ы. Раз в месяц, либо при воскресном запуске, читайте их часть — плавающие пять процентов покрывают весь репозиторий за пару месяцев, не удлиняя ни одну ночь:
restic check --read-data-subset=5%
borg check --verify-data # the Borg equivalent, slower and thorough
Всё, что было до сих пор, защищает от случайностей. Этот раздел защищает от злоумышленника, и как только видишь суть проблемы проектирования, становится не по себе: исходная машина каждую ночь должна достучаться до репозитория, а значит, она хранит рабочие учётные данные для этого репозитория. Кто владеет источником, тот владеет и этими учётными данными. Если учётные данные позволяют удалять, атакующий удалит — и современные программы-вымогатели делают это намеренно, ещё до того, как что-либо зашифровать, потому что жертва, способная восстановиться, не платит.
Решение — не более сложный пароль. Это асимметричное разрешение: источник может добавлять данные, но не может ничего удалять. Три способа это реализовать, в порядке убывания того, насколько легко сделать это правильно.
Первый способ — принудительный append-only через SSH. Это родная территория Borg и самый чистый вариант из доступных на машине, которой вы владеете. Запись в authorized_keys из шага 03 принудительно запускает borg serve --append-only, так что удалённая сторона отказывает в удалении, что бы ни просила ближняя. Prune всё равно должен выполняться, но выполняется он со стороны назначения, по расписанию, на которое источник не может повлиять, от имени аккаунта, до которого источник не может дотянуться. Атакующий с root на источнике может забить репозиторий мусором; он не может удалить архив за ночь до своего появления.
Второй способ — append-only REST-эндпоинт. У сопутствующей restic программы rest-server есть режим --append-only с тем же свойством: новые blob'ы принимаются, существующие удалить нельзя. Запустите его на приёмнике за TLS, укажите в RESTIC_REPOSITORY адрес https:// вместо sftp:, и форма защиты окажется идентичной. Это стоит одного дополнительного сервиса на приёмнике и даёт ту же гарантию.
Третий способ — object lock. На S3-совместимом хранилище версионирование вместе с периодом хранения object-lock делает удаление невозможным до истечения этого периода — это обеспечивает сам уровень хранилища, а не программа. Это самый строгий из трёх вариантов и самый дорогой, и он вводит аккаунт провайдера, а значит возвращает тот самый кастодиальный риск, который всё это руководство старается распределить. Разумно в качестве третьей копии, неудобно в качестве единственной.
Если ничего из этого не подходит — обычный SFTP, ключ, который может писать и, следовательно, удалять, — не делайте вид, что это не так: добавьте вторую, независимую копию, до которой источник вообще не может дотянуться. Pull вместо push: пусть приёмник сам входит на источник, читает то, что ему нужно, и сохраняет это у себя. Тогда учётные данные хранятся на машине, которая не выставлена наружу, и атакующий на источнике не найдёт ничего, что указывало бы на резервную копию. Настроить это чуть сложнее, зато риск разворачивается ровно в нужную сторону.
Какой бы вариант вы ни выбрали, проверьте его так же, как проверяли бы любой другой механизм защиты, — попытавшись его сломать. С источника, используя ключ бэкапа, попробуйте удалить данные. Правильный результат — отказ.
borg delete ::name-of-an-old-archive
# → Remote: Repository is in append-only mode. Refusing to delete.
У каждого сервиса есть небольшой фрагмент состояния, который не является ни данными, ни конфигурацией, и его потеря не повреждает сервис — она подменяет его другим сервисом, у которого просто совпадает имя. Старые ссылки перестают работать, федеративные узлы отказываются признавать новую личность, клиенты перевыпускают ключи. Вот эти фрагменты, по каждому стеку, для сервисов, которым посвящены руководства этого сайта.
Каталог данных, config/config.php и дамп базы данных. Включите режим обслуживания на время дампа и выключите после — иначе Nextcloud одновременно пишет в оба места.
Весь каталог данных: db.sqlite3, снятый через .backup, а не скопированный, плюс файлы rsa_key, вложения и sends.
Ключ подписи, homeserver.yaml, медиахранилище и дамп PostgreSQL. Потеряете ключ подписи — навсегда потеряете идентичность в федерации.
Само почтовое хранилище, таблицы виртуальных пользователей и приватные ключи DKIM — новый ключ DKIM означает, что все письма остаются неподписанными, пока не обновится DNS.
Сначала N8N_ENCRYPTION_KEY, затем дамп PostgreSQL. База данных без этого ключа — просто список воркфлоу, в котором ни одну учётную запись не прочитать.
Каталог скрытого сервиса. Эти несколько файлов и есть адрес .onion — без них сервис возвращается под другим именем, и все ссылки на него становятся мёртвыми.
/etc/wireguard целиком. Маленький, скучный — и вся разница между пятиминутным восстановлением и перевыпуском ключей на каждом вашем устройстве.
Seed-фраза и состояние каналов — на условиях самой ноды. Восстановление устаревших данных о каналах может стоить вам средств — следуйте процедуре резервного копирования конкретной реализации, а не обычному копированию файлов.
Спросите большинство людей, почему резервная копия должна быть в другом месте, и в ответ услышите: пожар, потоп или отключение электричества в дата-центре. Всё это правда, всё это встречается всё реже, и на всё это отвечают сто километров расстояния. Сбои, которые в 2026 году реально убивают бизнес, — административные, и одно лишь расстояние с ними ничего не делает.
Один аккаунт — одна точка отказа. Блокировка по автоматическому сигналу о мошенничестве, чарджбэк, пока вы были в самолёте, проверка на соответствие требованиям, требование об удалении, адресованное компании, а не машине, — каждое из этого одновременно достаёт все серверы под этим аккаунтом, включая тот, что хранит снапшоты. Техническая избыточность была безупречной и при этом бесполезной. Единственная структура, которая отвечает на это, — два провайдера или как минимум два аккаунта под двумя разными юридическими крышами.
Одна юрисдикция — одна юридическая рамка. Четыре северных бастиона не взаимозаменяемы — Швеция, Финляндия, Норвегия и Исландия защищают немного разные вещи, а Норвегия и Исландия вообще находятся вне Европейского союза. Разнести рабочую машину и её репозиторий между двумя из них означает, что копия не просто «где-то ещё» — она находится под вторым, независимым набором правил. Справочник по северным юрисдикциям разбирает законодательство по пунктам.
И приёмник ничего не узнаёт. Именно это делает разнесение по машинам дешёвым решением, а не компромиссом. И restic, и Borg шифруют данные ещё до того, как что-либо покидает источник, поэтому хост репозитория хранит blob'ы, ключа к которым у него нет. Он не знает, какие файлы у него хранятся, сколько их и как они называются. Вы не оказываете доверие второй стороне — вы арендуете диск, который не способен прочитать сам себя. Это же честный ответ на вопрос «стоит ли использовать в качестве цели для бэкапа провайдера, которому я не полностью доверяю?» — для зашифрованного репозитория доверие почти не имеет значения.
Задержка здесь роли не играет. От Helsinki до Reykjavík — 30 ms по магистрали, от Helsinki до Stockholm — 8 ms, от Stockholm до Oslo — 11 ms. Ежесуточной резервной копии нет дела ни до одной из этих цифр, как и восстановлению, скорость которого ограничена пропускной способностью диска. Выбирайте пару по юридическому расстоянию, а не по сетевому — сетевое расстояние внутри Nordics является статистической погрешностью.
Ещё кое-что стоит сказать прямо, потому что именно поэтому это руководство находится на этом сайте, а не в обычном блоге системного администратора: вторая машина заново открывает каждый вопрос, на который ответила первая. Если рабочий VPS был заказан без документа, удостоверяющего личность, и оплачен в Monero, а машина для резервных копий к нему заказана по корпоративной карте со сканом паспорта, — пара идентифицируема ровно настолько же, насколько её более слабое звено. Опорное руководство по анонимному VPS-хостингу разбирает три уровня — регистрацию, оплату и сеть — и они применимы к цели для резервного копирования буквально так же, как и к веб-серверу.
Репозиторий цел, зашифрован и навсегда нечитаем. Сохраните кодовую фразу — а для Borg ещё и экспортированный ключ — в менеджере паролей и на бумаге, ещё до первого снапшота.
Вместо дампа были скопированы рабочие файлы данных. Делайте дамп отдельным шагом перед резервным копированием и резервируйте именно дамп; никогда не указывайте в include-списке каталог данных работающего движка.
Учётные данные с правом записи на скомпрометированной машине — это учётные данные с правом записи для атакующего. Принудительно включайте append-only на приёмнике либо забирайте вторую копию через pull, а не push.
forget без prune только помечает снапшоты и не освобождает никакого места. Запускайте их вместе, держите свободное место на приёмнике и никогда не резервируйте образы контейнеров или node_modules.
Таймер перестал срабатывать после обновления, и об этом никто не сообщил. Добавьте юнит OnFailure= и раз в месяц проверяйте верх списка снапшотов — это займёт десять секунд.
Bind mount вне /opt, том, перенесённый во время миграции, exclude-шаблон, который захватил больше, чем предполагалось. Указывайте известный путь при каждом новом снапшоте и перечитывайте include-файл после любого изменения в стеке.
Десять вопросов, возникающих при настройке, — и ещё два, которые всплывают только потом, один раз.
Нет — это откат, и полезный, но он отказывает ровно в тех случаях, для которых и существует резервная копия. Снапшот живёт в том же аккаунте провайдера, что и сервер, который он копирует. Если аккаунт заблокирован, платёж оспорен, агент поддержки случайно удаляет что-то не то или атакующий получает доступ к вашей панели, — снапшот пропадает вместе с машиной. Он также не даёт никакой точечности: нельзя вытащить один удалённый вчера файл, можно только откатить весь диск целиком и потерять всё, что было после. Держите снапшоты — это самый быстрый способ отменить неудачное обновление, — но не путайте их с копией, которая существует там, куда исходная проблема не дотянется. Правило простое: если одни скомпрометированные учётные данные могут уничтожить обе копии, у вас на самом деле одна копия.
Оба шифруют на стороне клиента, оба дедуплицируют на уровне чанков, оба зрелые и оба справятся с задачей. Выбирайте restic, если хотите один статический бинарник, репозиторий, который может жить на SFTP, S3-совместимом объектном хранилище, REST-сервере или где угодно, куда дотянется rclone, и полное отсутствие чего-либо установленного на приёмнике. Выбирайте Borg, если приёмник — это подконтрольная вам SSH-машина, вам нужен самый жёсткий append-only без объектного хранилища с object-lock, и вам нравится его чуть более плотное сжатие. Практическая разница, которая решает дело: Borg требует borg на обоих концах и говорит только по SSH или с локальным путём; restic требует прохода prune, который ненадолго берёт эксклюзивную блокировку репозитория. Если не можете определиться — используйте restic: меньше движущихся частей на приёмнике стоит дороже любого бенчмарка.
Отталкивайтесь от размера того, что вы на самом деле резервируете, — а не от размера диска, на котором это лежит. Типичный самостоятельно размещённый стек — это несколько гигабайт базы данных и конфигурации плюс всё, что загрузили пользователи. Дедупликация и сжатие затем окупаются: ежедневный снапшот рабочего набора в 20 ГБ с политикой хранения на двенадцать месяцев обычно даёт репозиторий размером 40–80 ГБ, потому что повторно сохраняются только изменённые чанки. Sentinel ($3.90/мес, 120 ГБ NVMe) с запасом покрывает такой объём. Переходите на Garrison (240 ГБ, $7.90), когда несколько серверов резервируются в один и тот же репозиторий, и на Bulwark (960 ГБ, $32.90), когда сам рабочий набор достигает сотен гигабайт. Рассчитывайте размер под репозиторий, а затем удвойте его — репозиторий без свободного места не может выполнять prune, а репозиторий, который не может выполнять prune, только растёт.
Скопировать их вы можете. А вот восстановить — не факт. База данных, пишущая на диск в момент, когда вы её читаете, отдаёт вам набор файлов из нескольких разных моментов времени — это и есть определение «рваного» бэкапа: он восстанавливается как повреждённая база данных или, что хуже, как база, которая открывается нормально, но тихо теряет строки. Решение — дамп: pg_dump или pg_dumpall для PostgreSQL, mariadb-dump --single-transaction для MariaDB и MySQL на таблицах InnoDB, и sqlite3 db .backup out.db или VACUUM INTO для SQLite. Запишите дамп в файл, затем резервируйте этот файл. Если база слишком велика для ежедневного дампа, альтернативы — снапшот файловой системы, снятый при кратковременно приостановленном движке, или собственный инструмент физического резервного копирования движка — но для всего, что запускает один VPS, дамп — правильный и простой выбор.
Резервные копии пропадут безвозвратно. И restic, и Borg шифруют данные на стороне клиента, и ни у того, ни у другого нет пути восстановления, мастер-ключа или заявки в поддержку, которая разблокирует репозиторий за вас. В этом и суть: хост назначения — включая тот, что принадлежит не вам, — никогда не видит открытый текст ваших данных. Это также означает, что кодовая фраза теперь представляет собой ценность, равную всему, что она защищает, и она не должна храниться только на резервируемой машине. Положите её в менеджер паролей и сохраните физическую копию где-нибудь ещё. Для Borg также экспортируйте ключ репозитория командой borg key export и храните его отдельно; в режиме repokey ключ живёт внутри репозитория, поэтому репозиторий, до которого вы больше не можете добраться, уносит ключ с собой.
Вы выдаёте учётные данные, которые могут добавлять, но не могут удалять. Это самое важное архитектурное решение во всём упражнении, потому что современный вымогатель первым делом ищет конфигурацию резервного копирования и идёт по этому следу. Три способа этого добиться. Append-only через SSH: в authorized_keys получателя принудительно задайте команду borg serve --append-only --restrict-to-path /srv/borg — источник может записывать новые архивы и не может удалять старые. Append-only через REST: запустите rest-server от restic с --append-only и направьте репозиторий на него по HTTPS. Object lock: S3-совместимый бакет с версионированием и блокировкой хранения. Обычный SFTP ничего из этого не даёт — ключ, которым можно записать репозиторий, можно и стереть, — поэтому если ваш транспорт SFTP, дополните его второй копией, забираемой pull-запросом со стороны получателя, куда не дотягиваются учётные данные, перехваченные атакующим на источнике.
Задайте вопрос иначе: сколько работы вы готовы переделать? Это число и есть ваша целевая точка восстановления (RPO), и именно она задаёт интервал. Ежесуточный запуск подходит почти для любого самостоятельно размещённого сервиса — почтового сервера, Nextcloud, домашнего сервера Matrix, движка автоматизации. Почасовой имеет смысл, когда данные транзакционные и повторный ввод невозможен. Для политики хранения схема, которая выдерживает столкновение с реальностью, — --keep-daily 7 --keep-weekly 4 --keep-monthly 12: неделя точечного отката, месяц еженедельных контрольных точек, год ежемесячных, итого около 23 снапшотов. Длинный хвост важнее, чем принято думать, потому что он ловит не умерший диск, а повреждение или удаление, которое никто не замечал шесть недель.
Другое здание — это технический минимум; другая юрисдикция — то, что большинство пропускает, а потом жалеет. Пожар, потоп или отказ на уровне стойки решаются одним лишь расстоянием. Чего расстояние не решает, так это юридические и коммерческие риски: аккаунт, заблокированный одним провайдером, спор по платежу, требование об удалении, которое достаёт каждую машину под одной корпоративной крышей, судебный приказ, вручённый одной компании. Если обе копии живут у одного провайдера, одно письмо достаёт обе. Разделение пары между двумя нордическими бастионами — рабочая машина в Helsinki, репозиторий в Reykjavík, 30 ms друг от друга по магистрали — ничего не стоит по задержке и покупает вторую юридическую рамку. Данные в любом случае шифруются ещё до того, как покинут источник, так что приёмник, храня их, не узнаёт ничего.
Узкое место — не шифрование: современные процессоры шифруют по AES быстрее, чем гигабитный канал успевает передать результат, и оба инструмента используют аппаратное ускорение там, где оно есть. Узкое место — первая выгрузка, потому что всё новое: на аплинке 1 Gbps рабочий набор в 20 ГБ передаётся на скорости канала за несколько минут, и заметно дольше, если приёмник ограничивает скорость или файлов много и они мелкие. Каждый последующий запуск передаёт только изменившиеся чанки, что для типичного стека составляет десятки мегабайт и занимает меньше минуты. Если первый запуск конкурирует с продакшен-нагрузкой, ограничьте его скорость — restic принимает --limit-upload в КиБ/с, Borg принимает --upload-ratelimit — и запускайте его под nice и ionice.
Да, и дело не в паранойе — дело в том, что типичные сбои происходят тихо. Таймер, переставший срабатывать после обновления пакета. Exclude-паттерн, незаметно проглотивший каталог загрузок. Дамп базы данных, записанный по пути, который бэкап никогда не охватывал. Репозиторий, неделями проваливающий проверку целостности в лог, который никто не читает. Ни один из этих случаев не заявляет о себе сам; все они обнаруживаются за девяносто секунд восстановлением файла и взглядом на него. Делайте малое восстановление ежемесячно — вытащите один дамп базы и один каталог данных во временный путь и сравните их — и полную пересборку раз в год, на свежем VPS, с замером времени. Число, полученное из этой пересборки, — ваше реальное время восстановления, и оно почти никогда не совпадает с тем, что вы предполагали.
Sentinel — 120 ГБ NVMe за $3.90 в месяц, в Helsinki, Stockholm, Oslo или Reykjavík. Безлимитный трафик, так что восстановление ничего не стоит. Никакой почты при регистрации, никакого документа, удостоверяющего личность, и цель, которая не может прочитать ни байта из того, что хранит.
Последняя проверка · 2026-08-24 · Источники · Документация restic и BorgBackup, руководства по резервному копированию PostgreSQL и MariaDB, sshd authorized_keys(5), systemd.timer(5) · Периодичность · ежегодно
This guide is one spoke of a larger series. The pillar walks the three privacy layers end to end — the sibling spokes below dive into the specifics.
Three independent layers — signup, payment, network — explained, legal context included, common mistakes flagged.
SSH keys, ufw, fail2ban, kernel knobs, unattended-upgrades.
Deploy your own MCP server on a no-KYC VPS — TLS, streamable HTTP, OAuth.
Host an MCP server with no ID — the privacy stack, crypto-paid.
Перенесите агента с ноутбука — конфигурация, systemd, секреты, лимиты расходов.