Безопасность сайта

Бэкап, который не восстанавливается: как проверить копию заранее

Резервные копии есть, но развернуть их никто не пробовал. Как провести учебное восстановление, что проверять и как часто это повторять.

12 минут чтения 2 Дмитрий Акиндинов
Содержание
  1. Почему копия есть, а сайта нет
  2. Что должно входить в полную копию
  3. Учебное восстановление: пошагово
  4. Что проверять после разворачивания
  5. Сколько времени занимает восстановление
  6. Расписание проверок для малого бизнеса
  7. Правило трёх копий на человеческом языке
  8. Что сделать на этой неделе
  9. Частые вопросы

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

Самый неприятный разговор в моей работе выглядит так: сайт лежит, владелец говорит «у нас есть бэкапы, хостинг делает каждый день», мы идём восстановить сайт из резервной копии, и через сорок минут выясняется, что архивы битые, база в них месячной давности, а последний рабочий дамп кто-то удалил в апреле, когда чистил место на диске. Копии были. Сайта нет. В этой статье разберу, почему так происходит, как провести учебное восстановление на тестовой площадке и что именно проверять после разворачивания, чтобы узнать о проблеме заранее, а не в момент аварии.

Архивная коробка с резервной копией и стрелка восстановления

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

Почему копия есть, а сайта нет

За шесть лет обслуживания чужих сайтов я собрал коллекцию способов остаться без данных при формально настроенных бэкапах. Вот пять, которые встречаются чаще остальных.

Архив обрывается на середине. Скрипт запускается по крону, упирается в лимит времени выполнения или в переполненный диск, и падает. Файл на месте, размер выглядит правдоподобно, а внутри половина. Проверяется одной командой, но никто не проверяет.

База и файлы сняты в разное время. Файлы копируются в 3:00, дамп базы в 4:30. За полтора часа кто-то опубликовал товар, загрузил картинки, а плагин записал новую версию своих настроек. При восстановлении получаете набор ссылок на несуществующие файлы и таблицы, которые не совпадают со структурой темы.

Дамп снят без нужных таблиц или без данных. Классика: mysqldump с --no-data, потому что кто-то один раз копировал только схему и оставил флаг в скрипте. Или --ignore-table для таблицы сессий, а рядом случайно попал wp_options. Сайт разворачивается, но пустой.

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

Ротация съела всё, что было нужно. Хранение семь дней, сайт заражён три недели назад, вирус проявился вчера. Все семь копий уже с закладкой. Формально бэкапы есть и рабочие, но откатываться некуда. Отдельная больная тема, разобрана в материале про то, сколько хранить бэкапы сайта.

Общее у всех пяти пунктов одно: проблему видно только в момент восстановления. До этого момента система рапортует зелёным.

Что должно входить в полную копию

Владельцы обычно думают о бэкапе как о папке с сайтом. Реальная копия, из которой сайт поднимается за час, а не за неделю, состоит из шести частей.

ЧтоГде лежитЧем снимается
Файлы сайтаpublic_html/ целикомtar czf или rsync
База данныхMySQL/MariaDBmysqldump с ключами ниже
Почта на доменеящики на хостингевыгрузка через панель или IMAP-архив
Задания кронаcrontab пользователяcrontab -l > cron.txt
Конфиги сервера.htaccess, php.ini, nginx.confпопадают в архив файлов, если не забыть про скрытые
DNS-зонау регистратора или в панелискриншот или экспорт зоны

Две последние строки пропускают почти все. .htaccess не попадает в архив, если в tar не указан правильный набор путей, а редиректы, защита от ботов и правила кэширования живут именно там. DNS-зону никто не бэкапит вообще, а при переезде на другой хостинг после аварии вы будете руками вспоминать, какие были MX-записи для почты и куда указывал TXT для подтверждения домена в Яндексе.

Правильный дамп базы выглядит так. Флаги не для красоты: --single-transaction снимает консистентный срез без блокировки таблиц, --default-character-set спасает от кракозябр в русских текстах, --routines и --triggers забирают хранимые процедуры, которые есть у Битрикса и у самописных интеграций.

# Дамп базы с гарантией консистентности
mysqldump -u DB_USER -p'DB_PASS' DB_NAME 
  --single-transaction --quick --routines --triggers 
  --default-character-set=utf8mb4 
  | gzip -9 > db-$(date +%F-%H%M).sql.gz

# Файлы, включая скрытые и .htaccess в подпапках
tar czf files-$(date +%F-%H%M).tar.gz 
  -C /home/user/site.ru public_html

# Крон и версии окружения, чтобы не гадать потом
crontab -l > cron-$(date +%F).txt
php -v > env.txt && mysql --version >> env.txt

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

Учебное восстановление: пошагово

Учебное восстановление это разворачивание боевой копии на отдельной площадке с последующей проверкой. Никаких действий на живом сайте. Занимает от сорока минут до трёх часов в зависимости от размера, и делать это надо не в момент паники.

Шаг 1. Забрать архив как чужой человек

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

Шаг 2. Проверить целостность до распаковки

# Архив не оборван?
gzip -t files-2026-09-10-0300.tar.gz && echo "gzip ok"
tar tzf files-2026-09-10-0300.tar.gz > /dev/null && echo "tar ok"

# Дамп не пустой и заканчивается корректно?
gunzip -c db-2026-09-10-0300.sql.gz | tail -c 200
# должно быть: -- Dump completed on ...

# Сколько таблиц реально внутри
gunzip -c db-2026-09-10-0300.sql.gz | grep -c "CREATE TABLE"

Последняя строка про Dump completed спасала меня десяток раз. Если её нет, дамп оборвался, и восстановится он ровно до места обрыва: часть таблиц будет, часть нет, а MySQL при импорте отчитается об ошибке где-нибудь на четырёхсотой секунде.

Шаг 3. Поднять площадку

Подойдёт поддомен вида test.site.ru, закрытый от индексации и по возможности от посторонних. Локальный Docker или OpenServer тоже годится, но у него есть минус: локальная среда отличается от боевой версиями PHP и модулями, и часть проблем вы не увидите. Я предпочитаю тестовый поддомен на том же хостинге либо на резервном, куда планирую переезжать в случае аварии. Как это устроить малой кровью, разбирал в материале про staging для маленького сайта.

Обязательно: закрыть площадку от поисковиков в robots.txt и HTTP-авторизацией, иначе через месяц обнаружите в выдаче дубли своего сайта.

Шаг 4. Развернуть

# Файлы
tar xzf files-2026-09-10-0300.tar.gz -C /home/user/test.site.ru/

# База: создать пустую и залить дамп
mysql -u test_user -p'PASS' -e "CREATE DATABASE test_db 
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
gunzip -c db-2026-09-10-0300.sql.gz | mysql -u test_user -p'PASS' test_db

# Права после распаковки
find /home/user/test.site.ru -type d -exec chmod 755 {} ;
find /home/user/test.site.ru -type f -exec chmod 644 {} ;

Дальше правим доступы к базе в wp-config.php (или bitrix/php_interface/dbconn.php, или core/config/config.inc.php у MODX) и меняем домен в базе. Для WordPress через WP-CLI это одна команда, и она умеет чинить сериализованные массивы, которые ломаются при обычном SQL-replace:

wp search-replace 'https://site.ru' 'https://test.site.ru' 
  --all-tables --precise --report-changed-only
wp cache flush

Если WP-CLI недоступен, придётся идти через wp_options руками и потом отдельно чинить сериализацию в настройках темы. Это работает, но дольше и злее.

Что проверять после разворачивания

Открыть главную и увидеть логотип это не проверка. Открывшаяся главная означает только то, что PHP работает и база подключилась. Дальше идём по списку, он занимает минут пятнадцать.

  • Главная страница целиком: шапка, меню, слайдер, футер, реквизиты. Пустые блоки часто означают потерянные опции темы.
  • Внутренняя страница из глубины, не первого уровня. Проверяет, что правила ЧПУ восстановились.
  • Карточка товара или услуги с ценой. Проверяет кастомные поля.
  • Картинки: открыть любую в новой вкладке напрямую по URL. Битые превью означают, что папка uploads приехала не полностью или права не те.
  • Форма заявки: отправить тестовую и убедиться, что письмо или сообщение в Telegram дошло. На тестовой площадке отправку лучше перенаправить на свой ящик, а не на боевой.
  • Вход в админку под своим логином. Если пароль не подходит, значит таблица пользователей приехала из другого дампа.
  • Поиск по сайту и поиск по базе: SELECT COUNT(*) FROM wp_posts WHERE post_status='publish'; и сравнить с боевым. Расхождение в сотни записей видно сразу.
  • Дата последней записи или последнего заказа. Отвечает на вопрос, насколько свежая копия на самом деле.
  • Ошибки в логах: tail -50 error_log после первого обхода страниц. Молчаливые фаталы в фоне живут годами.

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

Результат учебного восстановления надо записать. Не в голову, а в файл: дата, из какой копии разворачивали, сколько заняло, что сломалось, что чинили руками. Через полгода этот файл будет ценнее самого бэкапа.

Сколько времени занимает восстановление

Это второй вопрос, ради которого проводят учения. Не «получится ли», а «за сколько». Владельцу нужна цифра, чтобы понимать, стоит ли сайт простоя в четыре часа или надо строить схему с горячей репликой.

Мои замеры по клиентам за последние два года, от момента «полез в облако за архивом» до момента «сайт открывается на боевом домене»:

СайтРазмер копииВремя восстановления
Визитка на WordPress, 20 страниц300 МБ25-40 минут
Корпоративный сайт с блогом на 200 статей1,5 ГБ1-1,5 часа
Магазин WooCommerce, 500 товаров4 ГБ2-3 часа
Битрикс Стандарт с интеграцией 1С12 ГБ4-6 часов
Тот же Битрикс, но восстановление на новый хостинг12 ГБ8-12 часов

Последняя строка объясняет, почему учения полезны: разница между «восстановить туда же» и «восстановить на новый сервер» двукратная минимум. Во втором случае добавляются установка нужной версии PHP, донастройка модулей, перенос почты, ожидание DNS. Если хостинг лёг целиком, вы попадаете именно во вторую строку, а не в первую.

Ещё одна деталь, которую видно только на учениях: скорость скачивания архива. 12 ГБ из объектного хранилища на канале в 20 Мбит/с это полтора часа только на загрузку. Если бэкап хранится в холодном классе хранения, добавьте время на разморозку. У Яндекс Объектного Хранилища холодный класс дешевле раза в три, но данные становятся доступны не мгновенно, и в аварии это неприятный сюрприз.

Расписание проверок для малого бизнеса

Проверять каждую копию вручную никто не будет, это неразумная трата времени. Нужен разумный минимум по двум уровням: автоматика каждый день и руки раз в квартал.

Каждый день, автоматически. Скрипт после снятия бэкапа проверяет целостность архива и отправляет результат в Telegram. Пять строк на bash, ставится один раз.

#!/bin/bash
F=/backup/files-$(date +%F).tar.gz
D=/backup/db-$(date +%F).sql.gz
MSG="Бэкап $(date +%F): "

if gzip -t "$F" 2>/dev/null && [ $(stat -c%s "$F") -gt 10000000 ]; then
  MSG+="файлы ok ($(du -h "$F" | cut -f1)), "
else
  MSG+="ФАЙЛЫ БИТЫЕ, "
fi

if gunzip -c "$D" 2>/dev/null | tail -c 100 | grep -q "Dump completed"; then
  MSG+="база ok"
else
  MSG+="БАЗА БИТАЯ"
fi

curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" 
  -d chat_id="$CHAT" -d text="$MSG" > /dev/null

Проверка размера через stat нужна, потому что пустой корректный gzip тоже проходит gzip -t. Порог берите по факту: если обычный архив 300 МБ, ставьте нижнюю границу мегабайт в 100.

Раз в квартал, руками. Полное учебное восстановление на тестовую площадку со списком проверок выше. Для визитки хватает раза в полгода, для магазина я делаю раз в квартал, для сайтов с оплатой на сайте раз в месяц.

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

Для клиентов на абонентке квартальные учения у меня входят в тариф, отчёт с временем восстановления и списком найденного приходит письмом. Что ещё входит в обслуживание и как считаются часы, смотрите в тарифах.

Правило трёх копий на человеческом языке

Есть старое правило 3-2-1, которое обычно пересказывают как заклинание. На языке владельца сайта на WordPress оно звучит так.

Три копии данных: боевой сайт и два независимых архива. Не два архива в одной папке, а именно два места.

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

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

Для малого бизнеса это выглядит так и стоит недорого:

  • ежедневный бэкап силами хостинга, хранение 7-14 дней (обычно бесплатно или в районе 100-200 рублей в месяц),
  • ежедневная выгрузка в Яндекс Объектное Хранилище или на Selectel через rclone, хранение 30 дней (в районе 30-80 рублей в месяц за типовую визитку),
  • ежемесячный полный архив, который скачивается на диск владельца и лежит просто так.

Итого рублей 150 в месяц против стоимости восстановления сайта с нуля, которая начинается от 30 000 и упирается в потолок, если восстанавливать нечего и контент придётся писать заново. Как собрать регулярное копирование на WordPress без покупки платных плагинов, я разбирал в отдельной статье.

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

Что сделать на этой неделе

Не откладывая и не заводя проект на квартал, сделайте три вещи.

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

Второе: скачайте свежий архив и прогоните по нему gzip -t и проверку на Dump completed. Пять минут, а результат бинарный: копия либо жива, либо нет.

Третье: назначьте дату учебного восстановления. Не «как-нибудь», а конкретное число в календаре. Первое учение почти всегда что-то находит: у меня из десяти первых прогонов у новых клиентов чистыми проходили один или два.

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

Частые вопросы

Как проверить, что бэкап рабочий

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

Что должно входить в полную копию

Файлы сайта и дамп базы данных, снятые в один момент. Копия только файлов для CMS бесполезна, копия только базы тоже. Штатный порядок резервного копирования WordPress описан в документации.

Как часто делать бэкапы

Ежедневно для сайта с заявками и заказами, раз в неделю для визитки, которая не меняется. Плюс отдельная копия перед каждым обновлением CMS.

Где хранить копии

Не на том же хостинге, где сайт. Отдельное хранилище стоит недорого, а при блокировке аккаунта или заражении сервера копия на нём пропадает вместе с сайтом.

Передайте техническую часть в надёжные руки.

Слежу за хостингом, бэкапами и обновлениями. Когда что-то пойдёт не так – звоните мне, не своему программисту, не в спортзал и не маме.

Короткие вопросы по теме

Все вопросы по теме "Безопасность сайта"

Заявка

Обсудим задачу

Напишите имя и телефон, выберите удобный канал. Перезвоню или напишу в течение часа в рабочее время.

Куда удобнее ответить?