
Резервное копирование VPS, которое реально восстановить
Почти у каждого, кто держит сервер, есть что-то, что он называет резервной копией. Гораздо меньше людей хоть раз её восстанавливали, и именно в промежутке между этими двумя фактами данные и исчезают на самом деле. Вот как построить копию, которая переживёт три вещи, по-настоящему уничтожающие серверы, — собственные руки, компрометацию и аккаунт, контроль над которым вы утратили, — не пристёгивая при этом незаметно проверенную личность к анонимной машине.
Мы храним три копии каждого байта, на котором работает эта сеть, и мы восстанавливались из них — намеренно, в обычный вторник — куда больше раз, чем нам когда-либо требовалось делать это в реальной аварии. Это не усердие ради усердия. Это единственный способ убедиться, что в репозитории лежит именно резервная копия, а не папка с зашифрованным шумом под обнадёживающим именем.
Советы ниже сознательно немодные. Здесь нет сервиса резервного копирования с дашбордом, ежемесячной подпиской и очередью поддержки — потому что каждая из этих вещей есть аккаунт, а аккаунт — это имя, карта и юрисдикция, три вещи, которые вы, возможно, приложили немало усилий, чтобы держать подальше от этого сервера. Здесь есть второй сервер, около сорока строк shell-кода и одна привычка, которую почти никто не соблюдает.
Всё это можно пройти за один вечер. Инструментарий рассчитан на Debian 13 и Ubuntu 24.04; на дистрибутивы семейства RHEL всё переносится с очевидными заменами. Если ваш сервер новый, сначала пройдите защиту в первый час, а уже потом переходите к этой странице — резервное копирование машины, которая уже принадлежит кому-то другому, всего лишь бережно сохраняет чужую работу.
Что на самом деле уничтожает данные на арендованном сервере
Спросите кого угодно, зачем он делает резервные копии, и в ответ обычно услышите: «на случай, если умрёт диск». На современном VPS это едва ли не самое маловероятное, что с вами может произойти. Ваш том лежит на NVMe в отказоустойчивом массиве, а гипервизор перенесёт вас с отказывающего хоста раньше, чем вы это заметите; отказ диска — проблема провайдера, и проблема давно решённая. Строить всю стратегию резервного копирования вокруг этого — всё равно что покупать огнетушитель для дома, который топит паводком.
А вот что на самом деле забирает у людей данные, примерно по убыванию частоты. На первом месте, причём с большим отрывом, — вы сами. rm -rf с переменной, развернувшейся в пустоту. DROP DATABASE не в том терминале, потому что две вкладки выглядели одинаково. Скрипт миграции, запущенный дважды. docker compose down -v, где -v — просто мышечная память. Это не экзотика; это обычный вторник.
На втором месте — приложения, удаляющие собственные данные. Обновление, которое запускает разрушительную миграцию и падает на середине. Плагин, очищающий каталог кеша, который на поверку кешем не был. Ротация логов, настроенная не на тот путь. На третьем месте — компрометация, и здесь стоит отметить: это тот случай, когда атакующий сам хочет уничтожить резервные копии, что меняет архитектуру решения — к этому мы ещё вернёмся ниже. На четвёртом месте, и о нём легко забыть на no-KYC хостинге, — потеря аккаунта. Баланс, ушедший в ноль, пока вас не было рядом, запись в менеджере паролей, которую вы так и не сделали, адрес почты, который вы забросили. Мы приостанавливаем сервер в течение нескольких часов после обнуления баланса и уничтожаем его через семь дней — и это намеренно немилосердно: именно эта арифметика позволяет нам не спрашивать, кто вы. Здесь нет сотрудника поддержки, который может найти вашу личность и сделать исключение, — потому что никакой личности искать негде.
Именно эта последняя категория отличает хостинг, построенный вокруг приватности, от массового, и именно она должна определять ваш план. Каждая мера, из-за которой этот сервер трудно связать с вами, одновременно делает трудным и для кого угодно — включая нас самих — вернуть его вам. Резервная копия — это то, что превращает такой обмен из риска в осознанный выбор.
Почему снимок — не резервная копия
Снимки — прекрасная вещь, и вам стоит ими пользоваться. Делайте снимок перед каждым обновлением ядра, каждой миграцией базы данных, каждый раз, когда собираетесь сделать что-то, что хотели бы иметь возможность откатить. Они восстанавливаются за секунды, стоят почти ничего и десяток раз в год спасут вам целый день.
Но резервными копиями они всё равно не являются — по трём структурным причинам, которые никаким маркетингом провайдера не отменить. Они живут внутри того же аккаунта: удалить их может всякий, кто способен войти в вашу панель управления, а если аккаунт перестаёт работать, снимки перестают существовать вместе с ним. Они находятся в том же домене отказа: тот же провайдер, та же control plane, часто тот же кластер хранения, а значит, одно неудачное событие вполне может забрать разом и оригинал, и копию. И они непрозрачны: снимок работающей машины фиксирует базу данных в момент записи ровно так же добросовестно, как и всё остальное, поэтому восстановление из снимка загруженного MySQL даёт вам сценарий восстановления после сбоя, а не чистую базу.
Старое правило 3-2-1 — три копии, на двух видах носителей, одна из которых вне площадки — обычно повторяют, не замечая, что средняя часть на арендованной инфраструктуре лишена смысла. У вас нет двух видов носителей. У вас есть один виртуальный блочный диск и ещё один виртуальный блочный диск, и оба они — чья-то чужая СХД. При переносе на VPS выживает та часть правила, которая была важна с самого начала: как минимум одна копия должна находиться там, куда не дотянется одно-единственное неудачное событие. Другой провайдер или как минимум другой регион, другие учётные данные, другой аккаунт, а если офшорный хостинг для вас — вопрос права, а не техники, — то и другая юрисдикция, чтобы одно судебное постановление не легло разом на обе копии.
Место назначения — часть вашей модели угроз
Этого раздела нет в других туториалах по резервному копированию, а между тем именно он важнее всего, если вы оплатили этот сервер в Monero.
Стандартный совет, который дают повсюду в интернете, — отправлять резервные копии push-ом в объектное хранилище: Backblaze B2, Amazon S3, Wasabi, Google Drive через rclone. Это дёшево, надёжно, работает. Но заодно одной командой перечёркивает изрядную часть того, что вы до этого делали. Для открытия такого аккаунта нужна карта и, как правило, документ, удостоверяющий личность. С момента загрузки первого же снимка этот провайдер хранит помеченную временем запись, связывающую ваше имя и платёжные данные с IP-адресом вашего сервера, — и обновляет её каждый час, вечно. Вы не подтверждали свою личность нам; вы подтвердили её им — и тем самым сами провели линию между этими двумя вещами.
Вторая половина ещё хуже, и именно её обычно упускают из виду. API-ключ, авторизующий эти загрузки, — это файл на продакшен-сервере. Тот, кто получит root на этой машине, — или кто угодно, кто законно завладеет диском, — получает не просто ваши данные. Он получает учётные данные, которые одним API-вызовом раскрываются до платёжной личности. Анонимный сервер превратился в указатель, направленный прямо на ваш банк.
Всё это не делает объектное хранилище неправильным выбором. Оно превращает его в осознанное решение, а не решение по умолчанию. Вот три пути — в порядке убывания того, насколько хорошо они сохраняют то, ради чего вы всё это затевали:
- Второй no-KYC VPS, в идеале в другой юрисдикции, чем продакшен, оплаченный с того же криптобаланса. Место назначения наследует свойства анонимности источника, а не противоречит им. Именно так поступаем мы, и именно это подразумевает вся оставшаяся часть руководства.
- Провайдер хранилища, принимающий крипту без идентификации. Такие существуют, они меньше по масштабу; прежде чем довериться маркетингу, проверьте, действительно ли путь оплаты свободен от идентификации, и уточните цену исходящего трафика заранее — а не в момент восстановления, когда будет уже поздно.
- Машина, которой вы физически владеете, забирающая данные pull-ом по WireGuard из-за вашего собственного домашнего подключения. Отлично для анонимности, плохо для доступности, а скорость восстановления при этом равна скорости исходящего канала у вас дома. Разумно в качестве третьей копии, слабо — в качестве единственной.
Что бы вы ни выбрали, аккаунт, хранящий резервные копии, не должен иметь общих учётных данных, адреса почты или пути восстановления доступа с аккаунтом, где живёт продакшен. Весь смысл в том, чтобы один скомпрометированный логин не давал доступа сразу к обоим. Это та же дисциплина, что и разделение личностей везде в остальной инфраструктуре, только применённая к наименее эффектному углу системы.
Push, pull и причина, по которой ransomware находит ваши резервные копии
Почти любой туториал по резервному копированию приходит к одной и той же архитектуре: cron-задача на продакшен-сервере, которая аутентифицируется в удалённом репозитории и пишет в него. Это просто, это работает, и у этого есть свойство, о котором никто не упоминает: продакшен-сервер хранит учётные данные, способные удалить весь репозиторий целиком. Прунинг старых снимков требует прав на удаление, а значит, ключ, которым запускается ваша ночная задача, — это тот же ключ, которым можно опустошить хранилище.
Подумайте, что это значит во время компрометации. Атакующему с root на машине не нужно искать ваши резервные копии — вы услужливо оставили ему рабочие учётные данные и конфиг с именем репозитория. Перечислить и уничтожить резервные копии до того, как запустить что-то заметное, — это не гипотетическая изощрённость, а стандартная практика, потому что именно она превращает инцидент в переговоры. Ваша ночная задача сама и провела разведку.
Есть три схемы, и разница между ними целиком сводится к тому, какая машина держит какой ключ.
Обычный push — это то, что описано выше. У продакшена есть права на чтение, запись и удаление. Удобно, и при этом полностью отказывает ровно в том сценарии, где резервная копия нужнее всего. Используйте эту схему, только если единственная угроза, которая вас реально волнует, — это ваши собственные пальцы.
Push в режиме append-only сохраняет ту же форму, но убирает опасный глагол. Репозиторий обслуживается чем-то, что понимает протокол и отказывается выполнять удаление: rest-server --append-only для restic или borg serve --append-only, принудительно заданный из authorized_keys. Продакшен может создавать новые снимки и не может удалять старые. Прунинг происходит позже, с другой машины, с другим ключом. Это серьёзное улучшение ценой примерно двадцати минут работы, и для большинства это разумная точка, где можно остановиться.
Pull переворачивает соединение. Резервный хост сам тянется в продакшен по SSH, копирует то, что ему нужно, и запускает инструмент резервного копирования локально, на собственном диске. У продакшена вообще нет никаких учётных данных для резервного копирования — там нечего искать, потому что машина даже не знает, где хранятся её резервные копии. Это самая надёжная схема, и именно её строит пошаговая инструкция ниже.
Будьте честны с собой насчёт цены pull-схемы, потому что она не бесплатна. Вы не устранили ключ, а просто переместили его: теперь резервный хост хранит SSH-ключ в продакшен, а значит, компрометация резервного хоста дотягивается вперёд, до боевой системы. Это более выгодный обмен — резервный хост ничего не запускает, не открывает наружу ничего, кроме SSH, и представляет собой куда менее привлекательную цель, чем публичный веб-сервер, — но это всё равно обмен, и большую часть оставшегося разрыва вы закрываете, принудительно ограничив этот ключ командой только для чтения, чтобы им нельзя было воспользоваться ни для чего, кроме чтения файлов.
Выбор инструмента: restic, Borg или обычный rsync
Три инструмента покрывают практически все случаи, и выбор между ними куда менее мучителен, чем можно подумать по форумным баталиям.
restic по умолчанию шифрует, дедуплицирует между снимками, умеет работать с SFTP, S3, REST и десятком других бэкендов и поставляется единым статическим бинарником, который можно бросить куда угодно. Формат его репозитория адресуется по содержимому, поэтому снимок обходится дёшево, а идентичные данные хранятся один раз, сколько бы машин их ни присылало. Издержки реальны, но скромны: ему нужна память, пропорциональная индексу репозитория, а прерванный запуск может оставить устаревшую блокировку, через которую следующий запуск отказывается переступать, пока вы не снимете её командой restic unlock. Это рекомендация по умолчанию, и именно на ней построены шаги ниже.
Borg дедуплицирует лучше, сжимает лучше и заметно быстрее работает на репозиториях с миллионами мелких файлов. Плата за это — связанность: Borg нужно ставить на обоих концах, версии должны близко совпадать, один репозиторий по-настоящему рассчитан на одного клиента, а нативного бэкенда для объектных хранилищ у него нет без прослойки-помощника. Если источник — большой почтовый спул или файловая система, забитая мелкими файлами, а обе машины под вашим контролем, Borg займёт вдвое меньше места. Его режим --append-only к тому же самая чистая реализация этой идеи из двух инструментов.
rsync — не инструмент резервного копирования, и попытки делать вид обратного заканчиваются одной добросовестно отзеркаленной копией повреждённого каталога. У него нет версионирования, нет дедупликации и нет шифрования данных в состоянии покоя; rsync --delete распространяет вашу ошибку на копию со скоростью канала. И тем не менее это правильный инструмент для перемещения байтов между двумя машинами, которые вы контролируете, — а именно эту работу он и выполняет в pull-схеме, пока restic берёт на себя версионирование и шифрование уже после того, как байты добрались до места. Используйте каждый инструмент по назначению.
Чего точно не стоит делать — городить самопальное решение на tar и имени файла с датой в названии. Оно работает примерно четыре месяца, пока не наступит день, когда диск заполнится, потому что ничего никогда не устаревало, или день, когда вы обнаружите, что полная копия набора данных на 40 ГБ каждую ночь — это 1,2 ТБ хранилища в месяц, за которые вы платите, чтобы держать тридцать почти одинаковых вещей.
Что включать в копию — и базы данных, которые вас предадут
Первый инстинкт — копировать всю файловую систему целиком. Сопротивляйтесь ему. Корневая файловая система — это в основном пакеты дистрибутива, которые переустанавливаются за девяносто секунд, и их включение обходится в место на диске, время передачи и, что хуже, во внимание — резервная копия на 40 ГБ, которую никто не хочет тестировать, полезна меньше, чем копия на 900 МБ, которую восстанавливают каждый квартал.
Список того, что действительно невозможно восстановить, короткий: /etc (вся ваша конфигурация и причина, по которой пересборка занимает час, а не выходные), /home и /root, /srv и /var/www, состояние приложений под /var/lib/ и /opt/, именованные тома Docker, написанные вами юниты cron и systemd и дампы ваших баз данных. Пропустите /proc, /sys, /dev, /run, /tmp, /var/cache, файлы подкачки, сокеты и /var/lib/docker/overlay2 — последний восстанавливается из вашего compose-файла и часто оказывается самым большим каталогом на диске.
Теперь часть, которая тихо губит восстановления. Копирование каталога данных базы во время её работы почти наверняка даёт набор файлов, который окажется непригодным к использованию. Движок держит состояние в памяти, пишет в несколько файлов в порядке, который имеет значение, а ваша копия проходит это дерево несколько минут, захватывая разные файлы в разные моменты. InnoDB иногда восстановится после сбоя из такой копии, а иногда нет; и это всплывёт месяцы спустя, во время восстановления, когда у вас и без того будет плохой день.
Вместо этого делайте дамп через движок, а затем копируйте уже сам дамп:
# MariaDB / MySQL — InnoDB gets a consistent snapshot from one transaction
mariadb-dump --single-transaction --quick --routines --triggers --events \
--all-databases | zstd -T0 > /var/backups/db/all-$(date -u +%F).sql.zst
# PostgreSQL — pg_dump is consistent by design; grab roles separately
pg_dumpall --globals-only > /var/backups/db/globals.sql
pg_dump -Fc -Z6 mydb > /var/backups/db/mydb.dump
# SQLite — never cp a live database; WAL mode makes that a torn file
sqlite3 /var/lib/app/app.db ".backup /var/backups/db/app.db"Стоит держать в уме две оговорки. --single-transaction даёт консистентность только для InnoDB — если какая-то таблица всё ещё MyISAM, она копируется вне транзакции и может оказаться несогласованной с остальными, так что такие таблицы стоит либо сконвертировать, либо блокировать. И раз уж вы делаете дамп, исключите рабочий каталог данных из файлового прохода. Резервное копирование и того, и другого означает хранение большой рваной копии рядом с хорошей — и предоставление будущему восстановлению двух кандидатов, один из которых незаметно неверен.
Контейнеры не меняют принцип, только путь: запускайте дамп через docker compose exec -T db mariadb-dump … и копируйте получившийся файл, а не том под ним.
Где живёт ключ шифрования
Шифрование на стороне клиента — это единственная причина, по которой вообще допустимо хранить ваши данные на машине, которая не является той, откуда они родом. И restic, и Borg шифруют данные ещё до того, как они покинут источник, так что резервный хост хранит только шифротекст и может считаться недоверенным хранилищем. Но это свойство стоит ровно столько, сколько стоит ваше обращение с ключом, — и ни на йоту больше.
Типичный сценарий провала до тоски банален. Пароль от репозитория лежит в /etc/restic/env на продакшен-сервере — это нормально и необходимо для автоматизации. А потом теряете именно продакшен — уничтоженный, изъятый или просто пропавший вместе с аккаунтом, — и вот вы смотрите на несколько сотен гигабайт зашифрованных блоков, медленно осознавая, что единственная копия ключа лежала внутри именно той вещи, потерю которой вы и пытались пережить.
Поэтому: пароль на сервере — это рабочая копия, а не оригинал записи. Сама запись должна жить там, где переживёт сервер, — в менеджере паролей, чьё хранилище само по себе бэкапится отдельно, или на бумаге в ящике стола, или и там, и там. Запишите рядом расположение репозитория и точную команду восстановления, потому что парольная фраза без контекста — это загадка, которую вам придётся разгадывать в стрессе через полтора года. И потратьте пять минут на проверку: с другой машины, имея на руках только то, что лежит в менеджере паролей, выполните restic snapshots для этого репозитория. Если сработало — у вас есть резервная копия. Если для этого нужно что-то, существующее только на продакшене, — у вас есть очень дорогая папка.
Оба инструмента поддерживают несколько ключей на один репозиторий (restic key add) — это чистый способ дать доступ задаче прунинга или второму администратору, не делясь исходной парольной фразой. А если продакшен-диск зашифрован с помощью LUKS, держите эти два секрета по-настоящему раздельно — хранить парольную фразу restic внутри LUKS-тома, а парольную фразу LUKS внутри репозитория restic — это петля, которая закрывается наглухо с обеих сторон.
Хранение: ловушка недельного срока
Срок хранения выглядит как вопрос стоимости дискового пространства, а на деле это вопрос задержки обнаружения проблемы. Значение имеет не то, сколько диска вы готовы потратить, а то, сколько времени проблема способна оставаться незамеченной в вашей системе. Каким бы ни был этот срок, ваша самая старая резервная копия должна быть старше него.
Семь ежедневных снимков кажутся щедрым запасом, а на практике оказываются тонким слоем. Повреждённая таблица, к которой никто не обращается, медленное удаление из-за неисправной cron-задачи, вторжение, которое тихо просидело месяц, прежде чем сделать что-то заметное, — всё это регулярно всплывает позже, чем через неделю, а если глубина вашей истории — семь дней, то каждый хранящийся у вас снимок уже заражён. Время скрытого присутствия при реальных вторжениях обычно измеряется неделями. Дешёвые ежемесячные снимки — конкретная защита именно от этого, а дедупликация делает их действительно дешёвыми: ежемесячный снимок, хранимый год, добавляет лишь долю полной копии, потому что повторно сохраняются только реально изменившиеся блоки.
restic forget \
--keep-daily 7 --keep-weekly 5 --keep-monthly 12 --keep-yearly 2 \
--pruneЭта лестница — неделя дней, месяц недель, год месяцев, пара лет годов — это около двадцати шести снимков, и на типичных данных они занимают заметно меньше, чем удвоенный размер одной полной копии. Это тот вариант по умолчанию, за который мы бы ратовали, если только у вас нет конкретной причины отступить от него.
Два эксплуатационных замечания. forget без --prune убирает только метки, поэтому место не освобождается, пока вы не выполните прунинг, — это удивляет тех, кто наблюдает за диском, который никак не уменьшается. А для --prune нужны права на удаление в репозитории, поэтому в append-only или pull-схеме эта команда не выполняется на продакшен-сервере. Она выполняется на резервном хосте или с вашего ноутбука, с ключом, которого продакшен никогда не видел. В этом разделении — весь смысл; не разрушайте его ради удобства на последнем шаге.
Автоматизация без тихого отказа
Классическая катастрофа резервного копирования — это не задача, которая падает с ошибкой. Это задача, которая перестаёт выполняться и никому об этом не сообщает, — а обнаруживают это одиннадцать месяцев спустя, когда кому-то она внезапно понадобилась. Каждый элемент ниже существует ровно для того, чтобы сделать именно этот исход невозможным.
Используйте systemd-таймер, а не cron. Вы получаете настоящие логи в журнале с прикреплёнными кодами завершения, Persistent=true — чтобы пропущенный из-за выключенной машины запуск происходил при следующей загрузке, а не пропадал навсегда, — и RandomizedDelaySec, чтобы целый парк серверов не обрушивался на резервный хост ровно в 03:00. Уведомление cron об ошибке — это письмо в локальный почтовый ящик, которое на современном сервере не приходит буквально никуда.
# /etc/systemd/system/backup.timer
[Unit]
Description=Nightly encrypted backup
[Timer]
OnCalendar=*-*-* 03:17:00
RandomizedDelaySec=1800
Persistent=true
[Install]
WantedBy=timers.targetЗатем добавьте контроль по принципу «мёртвой руки» — это самая ценная строчка во всём этом руководстве. В самом конце скрипта — после того, как резервное копирование завершилось успешно, а не до этого, — отправляйте HTTPS-запрос монитору, который ожидает вестей от вас каждый день и поднимает тревогу, если их не получает. Это переворачивает логику уведомлений: вместо того чтобы полагаться на то, что сбой сам породит сообщение, тревогой становится само отсутствие сигнала. Задача, которая была мертва три дня, тогда превращается в письмо у вас во входящих, а не в открытие посреди кризиса. Разверните монитор самостоятельно на резервном хосте, если не хотите заводить ещё один аккаунт у третьей стороны, — это несколько строк кода и собственный таймер.
И наконец, мелкие эксплуатационные опасности, с каждой из которых мы хотя бы раз сталкивались сами. Прерванный запуск оставляет блокировку, и каждый следующий запуск падает с сообщением, которое вы перестаёте читать уже на четвёртую ночь, — обрабатывайте restic unlock осознанно, а не рефлекторно. Заполненный диск назначения роняет каждую задачу, пока кто-нибудь не заметит; настройте оповещение по свободному месту, а не только по статусу задачи. SSH-ключ с истёкшим сроком действия, ротированный ключ хоста, правило nftables, добавленное во время совершенно не связанной работы, — каждое из этого молча обрывает pull. И systemctl enable --now, а не просто start: таймер, который никогда не был включён, прекрасно работает до первой перезагрузки — и больше не запускается никогда.
Тренировка восстановления
Всё описанное выше — это подготовка. А вот эта часть превращает её в настоящую резервную копию, и именно её почти все пропускают.
Раз в квартал разворачивайте свежий VPS — тарифа Starter за $5 более чем достаточно, а оплата почасовая с посекундной тарификацией, так что всё упражнение обойдётся в пару центов. Восстанавливайтесь на него, используя только то, что было бы у вас при настоящей катастрофе: адрес репозитория, парольную фразу из менеджера паролей и записанную процедуру. Намеренно не используйте ничего с продакшена, потому что в сценарии, который вы репетируете, продакшена не существует. Поднимите приложение, направьте запись в hosts-файле на новый IP, покликайте по интерфейсу. Затем уничтожьте машину.
# on a throwaway machine, with nothing but the passphrase
export RESTIC_REPOSITORY=sftp:[email protected]:/srv/restic/prod
restic snapshots # can you even see the history?
restic restore latest --target /restore
restic check --read-data-subset=10% # verify stored blocks, not just metadataТо, что при этом обнаруживается, никогда не совпадает с ожиданиями. Это конфигурационный файл, лежавший в каталоге, который не охватывал список include. Дамп базы данных, который весит ноль байт уже пять недель, потому что сменился пароль, а скрипт не проверил код завершения. Приложение, которое не запускается, потому что секрет живёт в переменной окружения, которую держит оркестратор, и никогда не лежал в файловой системе вообще. Незадокументированная DNS-запись. Сертификат, который нужно перевыпустить, прежде чем хоть что-то ответит на 443. Каждая из этих вещей — это десять минут работы тихим днём и уродливые два часа в три ночи.
Запишите, сколько времени заняла тренировка от начала до конца. Именно это число — а не частота резервного копирования — и есть ваше реальное время восстановления, и это единственный честный ответ на вопрос, сколько вы будете простаивать. Добавьте также в ежемесячный таймер команду restic check --read-data-subset=5%: она читает и проверяет вращающуюся выборку реально сохранённых блоков, а не только индекс, — именно так вы находите тихую порчу данных, пока у вас ещё есть на что откатиться. А если вам когда-нибудь предстоит перенести весь сервер на новый хостинг, отрепетированное восстановление — это уже большая часть работы по миграции.
- Опишите, что действительно нельзя пересобрать
Прежде чем браться за инструменты, составьте список. Пройдитесь по машине и для каждого каталога задайте вопрос: если он исчезнет, смогу ли я воссоздать его из пакетного менеджера, git-репозитория или compose-файла? Если да — ему не место в резервной копии. То, что останется, обычно оказывается гораздо меньше, чем ожидают, — конфигурация, пользовательские данные, состояние приложений, дампы баз данных.
# the fast way to find what is actually big and stateful du -x -h -d2 / 2>/dev/null | sort -rh | head -30 docker volume ls # named volumes are state; overlay2 is notПревратите результат в два явных файла,
/etc/restic/include.txtи/etc/restic/exclude.txt. Явные списки лучше хитрых выраженийfind, потому что их можно проверить глазами, а появление нового каталога на сервере должно быть осознанным решением, а не незаметным попаданием в копию. - Разверните резервный сервер в другой юрисдикции
Закажите второй VPS в регионе, отличном от того, где работает продакшен, — если продакшен в Париже, разместите копию в Рейкьявике или Бухаресте. Смысл в том, чтобы ни одно отдельное правовое или физическое событие не могло дотянуться сразу до обеих машин. Тариф Starter за $5/мес даёт 80 ГБ NVMe, чего после дедупликации хватает на очень долгую историю типичного небольшого сервера; годовой цикл оплаты на 12 месяцев снижает эту цену вдвое. Платите с того же криптобаланса, чтобы место назначения наследовало анонимность источника, а не противоречило ей.
Заведите для него отдельный от продакшена аккаунт, если хотите полной изоляции учётных данных. Затем защитите его точно так же, как и любую другую машину, — SSH только по ключам, файрвол с политикой default-deny, — и не устанавливайте на него больше ничего. Ценность этой машины именно в её скучности: ни веб-сервера, ни открытых портов, кроме SSH, нечего эксплуатировать из интернета.
- Создайте дверь только для чтения из резервного хоста в продакшен
Именно этот шаг превращает схему в pull. На резервном хосте сгенерируйте отдельный ключ. Затем установите его публичную половину на продакшене с принудительной командой, чтобы этот ключ мог делать ровно одну вещь: читать файлы. Он не может открыть шелл, пробросить порт или что-либо записать.
# on the backup host ssh-keygen -t ed25519 -a 100 -f ~/.ssh/pull_prod -C "pull@backup" # on production, in /root/.ssh/authorized_keys — one line command="/usr/bin/rrsync -ro /",restrict ssh-ed25519 AAAAC3Nz… pull@backuprrsyncпоставляется вместе с rsync (/usr/bin/rrsyncна Debian 13;/usr/share/doc/rsync/scripts/rrsyncв более старых релизах), а-roзаставляет его отказываться от всего, кроме чтения. Словоrestrictодним махом отключает проброс портов, проброс агента, выделение PTY и X11. Проверьте, что клетка действительно держит, прежде чем на неё полагаться, — командаssh -i ~/.ssh/pull_prod root@productionобязана не суметь дать вам шелл. - Делайте дамп баз данных на продакшене по собственному расписанию
За продакшеном остаётся ровно одна задача: делать консистентные дампы в промежуточный каталог, который потом заберёт pull. Для этого не нужны никакие учётные данные для резервного копирования, и именно поэтому схема работает.
# /usr/local/sbin/dump-db.sh (chmod 700) set -euo pipefail D=/var/backups/db; install -d -m 700 "$D" mariadb-dump --single-transaction --quick --routines --triggers --events \ --all-databases | zstd -T0 > "$D/all.sql.zst.tmp" mv "$D/all.sql.zst.tmp" "$D/all.sql.zst"Обратите внимание на запись-во-временный-файл-с-переименованием: она гарантирует, что pull никогда не заберёт наполовину записанный дамп, вне зависимости от таймингов.
set -euo pipefail— это не украшение: без него упавшийmariadb-dumpотправит по конвейеру пустой поток вzstd, который завершится успешно, и вы получите вполне валидный сжатый файл, в котором нет вообще ничего. Это самый распространённый способ незаметно получить бесполезную резервную копию. Запускайте этот скрипт из собственного таймера за полчаса до pull. - Заберите данные на резервный хост
На резервном хосте забирайте rsync-ом список include с продакшена в промежуточное дерево каталогов. По сети идут только изменившиеся блоки, поэтому после первого запуска это быстро и дёшево.
# /usr/local/sbin/pull.sh (chmod 700, runs on the BACKUP host) set -euo pipefail [email protected] # -r is spelled out on purpose: with --files-from, -a does NOT imply # recursion, and without it you silently copy empty directories. rsync -aHAX -r --delete --numeric-ids \ -e "ssh -i /root/.ssh/pull_prod -o BatchMode=yes" \ --files-from=/etc/backup/include.txt \ --exclude-from=/etc/backup/exclude.txt \ "$SRC:/" /srv/staging/prod/-aHAXсохраняет жёсткие ссылки, ACL и расширенные атрибуты, а--numeric-idsсохраняет осмысленность владения файлами между машинами, у которых различается/etc/passwd.--deleteздесь безопасен именно потому, что промежуточное дерево — это ещё не резервная копия: версионированная история живёт в репозитории restic, который строится на следующем шаге, так что удаление, распространившееся в промежуточный каталог, всё ещё можно восстановить из вчерашнего снимка. - Инициализируйте репозиторий и сохраните ключ офлайн
Всё ещё на резервном хосте создайте локальный репозиторий restic и загоните в него промежуточное дерево. Локальность означает отсутствие сети в горячем пути, отсутствие удалённых учётных данных, которые можно украсть, и восстановление на скорости диска.
apt install -y restic install -d -m 700 /etc/backup openssl rand -base64 32 > /etc/backup/pass # write this into your password manager NOW chmod 600 /etc/backup/pass export RESTIC_REPOSITORY=/srv/restic/prod export RESTIC_PASSWORD_FILE=/etc/backup/pass restic initСкопируйте эту парольную фразу в менеджер паролей, а рядом запишите путь к репозиторию и команду восстановления. Затем докажите это себе: с третьей машины, используя только менеджер паролей, выполните
restic -r sftp:backup@…:/srv/restic/prod snapshots. Если список снимков выводится — ключ действительно восстановим. Если для этого нужно что-то, существующее только на одном из серверов, — почините это сейчас, а не в момент, когда обнаружите проблему позже. - Поставьте на расписание — и превратите тишину в тревогу
Оберните pull, запуск restic и прунинг в один скрипт и запускайте его через systemd-таймер. Обратите внимание, что
forget --pruneздесь безопасен, потому что резервный хост на законных основаниях владеет репозиторием — продакшен ни в какой момент не держал учётных данных с правом удаления.# tail of /usr/local/sbin/backup.sh restic backup /srv/staging/prod --tag nightly restic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 \ --keep-yearly 2 --prune curl -fsS -m 10 --retry 3 https://hc.example.net/ping/<uuid> # only on successsystemctl daemon-reload systemctl enable --now backup.timer systemctl list-timers backup.timer # confirm the next run is where you expectcurlвыполняется, только если все предыдущие команды завершились успешно, — благодаряset -e. Монитор на другом конце ожидает ежедневный пинг и поднимает тревогу при его отсутствии, что превращает тихо остановившуюся задачу в письмо, а не в археологическую находку.enable --now, а не простоstart, — таймер, который никогда не был включён, доживает ровно до первой перезагрузки. - Проведите тренировку восстановления и запишите, сколько она заняла
Поставьте повторяющееся напоминание там, где реально его увидите, — раз в квартал. Разверните одноразовый Starter, восстановитесь на него, имея на руках только парольную фразу и записанную процедуру, поднимите приложение, убедитесь, что оно отдаёт настоящие данные, уничтожьте машину. Почасовая оплата означает, что вся тренировка обойдётся в пару центов.
restic restore latest --target /restore zstd -dc /restore/var/backups/db/all.sql.zst | mariadb systemctl start nginx app curl -H 'Host: example.com' http://127.0.0.1/healthЗафиксируйте затраченное время и каждый сюрприз, с которым столкнулись, а затем исправьте эти сюрпризы в скрипте резервного копирования, а не только в памяти. Именно это затраченное время — ваша настоящая цель по времени восстановления; пока вы не измерили его хотя бы раз, любая названная цифра — просто догадка.
Куда класть копию
| Место назначения | Цена анонимности | Деньги | Скорость восстановления | Переживает |
|---|---|---|---|---|
| Второй no-KYC VPS в другом регионе | Никакой — оплачен с того же криптобаланса, личности нигде нет | $5/мес, вдвое дешевле при оплате на 12 месяцев | Быстро — между дата-центрами на скорости 1–10 Гбит/с | Потерю аккаунта, компрометацию, одну юрисдикцию, ваши собственные ошибки |
| Снимок провайдера | Никакой | Дёшево | Секунды | Почти ничего — тот же аккаунт, тот же домен отказа, умирает вместе с обоими |
| Объектное хранилище (B2 / S3 / Wasabi) | Высокая — карта и удостоверение личности на аккаунте, а API-ключ на вашем сервере указывает прямо на него | Хранение очень дешёвое, но плата за исходящий трафик кусается при восстановлении | Быстро, если вы готовы оплатить исходящий трафик | Компрометацию и потерю аккаунта — ценой имени, привязанного к машине |
| Провайдер хранилища, принимающий крипту | Низкая, если путь оплаты действительно свободен от идентификации, — проверяйте, а не верьте на слово | Умеренно | Очень по-разному — уточняйте лимиты на исходящий трафик заранее | Почти всё, но с большим риском контрагента, чем у VPS под вашим контролем |
| Домашний NAS, забираемый pull-ом по WireGuard | Никакой — из вашей собственной сети ничего не уходит ни в незашифрованном, ни в атрибутируемом виде | Железо, которое у вас уже есть | Медленно — упирается в скорость исходящего канала у вас дома | Всё, кроме вашего дома; слабо в роли единственной копии, отлично в роли третьей |
| Ничего / «оно же в git» | Никакой | Бесплатно | Никогда | Ничего. Git хранит ваш код; он не хранит вашу базу данных, ваши загруженные файлы и ваш /etc |
Ответы на актуальные вопросы
Разве снимок провайдера — это уже не резервная копия?
Нет, и это различие не педантизм. Снимок живёт в том же аккаунте, под теми же учётными данными, в том же домене отказа, что и копируемый им сервер, — а значит, не переживает потерю аккаунта, и удалить его вместе с оригиналом может всякий, кто дотянется до вашей панели управления. К тому же он снимается с работающей машины, а значит, загруженная база данных фиксируется прямо в момент записи. Используйте снимки как кнопку отмены перед рискованными изменениями; используйте зашифрованный репозиторий за пределами сервера как настоящую резервную копию.
restic или Borg — что выбрать?
restic — если только у вас нет конкретной причины поступить иначе. Он по умолчанию шифрует, не требует ничего устанавливать на приёмной стороне, умеет работать с любым бэкендом, который вообще стоит использовать, и поставляется одним статическим бинарником. Borg дедуплицирует и сжимает лучше и заметно быстрее на файловых системах с миллионами мелких файлов, но его нужно ставить и синхронизировать по версиям на обоих концах, и он по-настоящему рассчитан на одного клиента на репозиторий. Большой почтовый спул и обе машины под вашим контролем — Borg. Всё остальное — restic.
Сколько места на диске нужно резервному серверу?
Для дедуплицированного репозитория с лестницей хранения из этого руководства — 7 ежедневных, 5 еженедельных, 12 ежемесячных, 2 годовых — закладывайте примерно в два-три раза больше размера данных, которые вы реально копируете, а не всего сервера. Двадцать шесть снимков не значат двадцать шесть копий, потому что повторно сохраняются только изменившиеся блоки. Небольшой сайт с 15 ГБ реального состояния спокойно помещается на тариф Starter с 80 ГБ, да ещё и с запасом на годы истории.
Разве второй VPS не удваивает счёт за хостинг?
Только если резервный сервер повторяет конфигурацию продакшена, а этого делать не нужно. Он не запускает приложение и не обслуживает трафик; ему нужен диск, а не CPU. Тариф Starter за $5 рядом с продакшен-сервером за $30 — это одна шестая счёта, а цикл оплаты на 12 месяцев снижает и её на 50%. На фоне цены потери всего, это самая дешёвая строчка в счёте — а заодно резервный сервер служит и машиной для тренировки восстановления.
Где должен храниться пароль от репозитория?
В менеджере паролей, чьё хранилище само по себе резервируется где-то ещё, или на бумаге, или и там, и там — а рядом с ним адрес репозитория и точная команда восстановления. Копия в /etc на сервере — это рабочая копия для автоматизации, а не оригинал записи. Проверяйте по-настоящему: с машины, которая не является ни продакшеном, ни резервным хостом, используя только то, что лежит в менеджере паролей, выполните restic snapshots. Если требуется что-то ещё, восстанавливаемой резервной копии у вас пока нет.
Как часто должно выполняться резервное копирование?
Спросите себя, сколько работы вы готовы переделать заново. Для большинства серверов подходит ежедневный запуск ночью: теряется максимум день, а с одним ночным прогоном легко разобраться. Загруженной базе данных нужно чаще — обычная схема: дампы базы каждый час, а полный файловый проход остаётся ночным. Дальнейшее увеличение частоты даёт меньше пользы, чем те же усилия, вложенные в более долгий срок хранения и настоящую тренировку восстановления, — а именно там и живёт реальный риск.
Можно ли делать резервные копии сервера, диск которого зашифрован LUKS?
Да, и эти два механизма дополняют друг друга, а не дублируют. LUKS защищает диск, пока машина выключена; резервная копия защищает вас от удаления, порчи данных и полной потери машины. Копируйте примонтированную файловую систему точно так же, как незашифрованную, — restic снова шифрует данные на выходе, так что репозиторий остаётся в безопасности даже на недоверенном хранилище. Держите эти два секрета в по-настоящему раздельных местах: парольная фраза LUKS, хранящаяся только в репозитории restic, или парольная фраза restic, хранящаяся только внутри тома LUKS, — это петля, которая захлопывается наглухо с обеих сторон.
Если мой сервер скомпрометируют, резервные копии в безопасности?
Это целиком зависит от одного решения, которое вы приняли месяцы назад. Если продакшен-сервер хранит учётные данные с правом удаления в репозитории, то нет — атакующий сначала перечислит и уничтожит резервные копии, потому что именно это превращает инцидент в переговоры. Если же репозиторий работает в режиме append-only, либо резервный хост сам забирает данные pull-ом, а у продакшена вообще нет учётных данных для резервного копирования, то история выживает, и вы восстанавливаетесь на снимок до вторжения. В этом и состоит весь смысл pull-схемы — и причина выбрать её заранее, до того как она понадобится.
Keep exploring
Перенос VPS без простоя
Отрепетированное восстановление — это уже большая часть миграции; здесь — всё остальное, включая тайминг DNS, который на самом деле и решает длительность простоя.
Шифрование диска VPS с помощью LUKS
Дополнение к этой странице: резервные копии защищают вас от потери данных, а LUKS защищает их на диске, которым вы физически больше не владеете.
Защита нового VPS в первый час
Сделайте это с резервным хостом, прежде чем доверить ему что-либо, — SSH только по ключам и файрвол с политикой default-deny, за какой-то час.
Deploy your offshore server.
Выберите регион. Выберите план. Вставьте ключ. Оплатите. Следующие 47 секунд — за нами.