Бэкап магазина: почему копии от хостера недостаточно
«У хостера есть бэкап» звучит успокаивающе, пока не выясняется, что копия недельной давности, а вчера затёрли базу заказов. Разбираю, из чего на самом деле состоит рабочий бэкап WooCommerce-магазина — и почему проверенное восстановление важнее самого факта резервной копии.
COS / KNOWLEDGE BASE
# Бэкап магазина: почему копии от хостера недостаточно
Я как-то присутствовал на звонке, который начинался спокойно, а закончился в тишине, которую слышно даже через динамик телефона. Владелец интернет-магазина автозапчастей звонил в техподдержку хостинга с одной простой просьбой — откатить базу данных на вчерашний день, потому что стажёр, разбирая склад «ненужных» категорий, снёс полторы тысячи товаров вместе с историей заказов по ним. Саппорт хостинга бодро ответил: «Не переживайте, у нас есть резервное копирование, всё восстановим». Пауза. Потом уточняющий вопрос от клиента: «А за какое число копия?» И вот тут началось интересное — оказалось, что тарифный план предполагает бэкап раз в неделю, хранится он семь дней, и последняя доступная копия — это состояние магазина шестидневной давности. То есть между «восстановим» и «восстановим то, что вам нужно» пролегала пропасть в шесть дней активных продаж, новых заказов, изменений цен и добавленных товаров. Владелец слушал это молча секунд десять, а потом сказал фразу, которую я запомнил дословно: «То есть у меня нет бэкапа. У меня есть бэкап того, что было неделю назад».
Это не редкий случай и не невезение конкретного человека. Это системная особенность того, как большинство людей понимают слово «бэкап» применительно к своему хостингу. Практически любой более-менее приличный хостинг-провайдер в маркетинговых материалах пишет что-то вроде «регулярное резервное копирование включено в тариф». И это правда — только эта правда сформулирована так, чтобы звучать значительно лучше, чем есть на самом деле. Регулярное — это может означать раз в сутки, а может означать раз в неделю. Включено — может означать «мы делаем снимок всего диска целиком», а может означать «мы бэкапим только файлы, без базы данных, потому что база у вас в отдельном managed-сервисе». Хранится — может означать тридцать дней, а может означать три. И самое неприятное: почти никогда в этих маркетинговых обещаниях не сказано ни слова о том, что происходит, когда вам действительно нужно восстановиться. Сколько это займёт времени. Можно ли восстановить только одну таблицу, а не всю базу целиком. Что будет с файлами, загруженными после момента бэкапа. Хостер продаёт вам страховку от катастрофы, но не рассказывает про франшизу, сроки выплаты и список исключений — а они там есть, поверьте.
Добавлю ещё один слой к этой истории, потому что он часто всплывает уже после разговора о частоте копий — вопрос о том, что именно попадает в бэкап хостера. Многие тарифы бэкапят диск целиком, включая файлы, но не гарантируют консистентный снимок базы данных в момент высокой нагрузки: если копирование происходит одновременно с записью новых заказов, можно получить архив, где файлы уже «из завтра», а таблица заказов — ещё «из вчера», и при восстановлении получится состояние, которого на самом деле никогда не существовало. Для блога это почти никогда не критично. Для интернет-магазина, где целостность связки «заказ — позиции заказа — статус оплаты» имеет значение, такая рассинхронизация может создать проблемы, которые всплывут не сразу, а через недели, когда бухгалтерия не сойдётся с платёжным шлюзом.
Я это рассказываю не для того, чтобы демонизировать хостинг-провайдеров — они делают ровно то, за что им платят, и делают в среднем неплохо на своём уровне ответственности. Проблема в другом: владелец бизнеса воспринимает «бэкап от хостера» как страховку от всего на свете, хотя по факту это в лучшем случае страховка от отказа диска на стороне сервера. А данные интернет-магазина теряются совершенно другими способами, и большинство этих способов хостерский бэкап либо не покрывает вообще, либо покрывает так, что толку от этого покрытия оказывается немного. Дальше я хочу пройтись по тому, что реально угрожает данным WooCommerce-магазина, из чего должен состоять бэкап, чтобы от него была практическая польза, и почему восстановление — это отдельная дисциплина, а не кнопка «вернуть как было».
Три способа потерять то, что жалко терять
Первый и самый частый сценарий — кривое обновление. Казалось бы, тривиальная вещь: вышло обновление плагина, вы его поставили, сайт лёг. Но дьявол здесь не в самом падении сайта — упавший сайт вы заметите за минуты и, скорее всего, откатите плагин руками. Дьявол в том, что многие обновления не «падают» эффектно с белым экраном, а тихо ломают часть функциональности. Обновился плагин синхронизации с маркетплейсом — и он начал затирать поле с ценой у половины товаров, потому что маппинг полей в новой версии слегка изменился. Никто этого не заметил три дня, потому что сайт работал, оформлялись заказы, ничего не «падало» в привычном понимании. А когда заметили — три дня назад откатывать плагин уже поздно, испорченные данные накопились, и единственный способ вернуть всё как было — это база данных на момент до обновления. Если у вас есть только недельный бэкап хостера, а проблему обнаружили на четвёртый день, вы восстановите состояние ДО того, как товар вообще появился в каталоге, и потеряете все заказы за неделю в придачу.
Второй сценарий — взлом со следами шифрования или дефейса. Я разбирал это подробно в статье про защиту WooCommerce-магазина от WAF до геоблокировки, и суть там простая: WordPress и WooCommerce — это массовые цели для автоматизированных атак, и рано или поздно кто-то находит дыру. Иногда это классический дефейс — на главной странице появляется баннер с требованием выкупа. Иногда куда тише: в код темы дописывается скрипт, который перенаправляет часть трафика на партнёрские сайты мошенников, а вы узнаёте об этом только когда падает конверсия и Google начинает помечать сайт как небезопасный. В обоих случаях единственный надёжный способ вернуть контроль над ситуацией — откатиться на версию файлов и базы данных до момента компрометации. Проблема в том, что момент компрометации редко совпадает с моментом обнаружения. Если злоумышленник просидел в системе три недели, а ваш хостер хранит бэкапы за последние семь дней — доступной чистой копии просто не существует. Она успела перезаписаться.
Третий сценарий — самый обидный, потому что в нём никто не виноват в классическом смысле, просто человек ошибся. Контент-менеджер вместо того чтобы отфильтровать категорию «Распродажа» для массового снятия с публикации, случайно выбрал фильтр по всему каталогу и нажал «Перевести в черновик» для четырёх тысяч товаров. Или бухгалтер, выгружая отчёт, случайно запустил скрипт, который должен был работать на тестовой базе, а сработал на боевой. Я видел похожую историю на одном проекте — не буду называть нишу, чтобы не подставлять людей — где новый сотрудник, разбираясь с настройками атрибутов, массово применил операцию «удалить термин» не к тестовому дублю таксономии, а к боевой, и потерял привязку полутора тысяч товаров к нужным фильтрам. Восстанавливать вручную — это не часы, это дни ручной работы, потому что связи между товарами и атрибутами не хранятся где-то в одном удобном месте, они размазаны по десяткам таблиц.
Что объединяет все три сценария? Ни один из них не является отказом оборудования хостинга. Диск не сломался, сервер не сгорел, дата-центр не затопило. Это именно то, от чего хостерский бэкап худо-бедно защищает — и правильно делает, это его работа. Но реальная статистика потери данных на моей практике выглядит иначе: подавляющее большинство инцидентов — это человеческий фактор, программные ошибки и целенаправленные атаки, а не физический отказ железа. И вот именно от этого класса угроз собственный, управляемый бэкап-модуль защищает кардинально лучше, чем усреднённая услуга хостинг-провайдера, потому что даёт вам контроль над двумя параметрами, которые в чужом бэкапе вы не контролируете вообще: частота создания копий и глубина хранения истории.
Позволю себе прикинуть цену вопроса на пальцах, потому что абстрактная «потеря данных» пугает меньше, чем конкретная сумма. Магазин со средним чеком в четыре тысячи рублей и полусотней заказов в день теряет за шесть дней недоступного бэкапа примерно двенадцать тысяч заказов на круг, из которых даже часть, не оформленная повторно вручную, — это сотни тысяч рублей упущенной выручки, не считая часов работы менеджеров на восстановление вручную того, что можно было бы поднять одним нажатием кнопки. И это без учёта репутационных потерь, если клиент, оформивший заказ во время инцидента, вообще не получит подтверждения и просто уйдёт к конкуренту. Когда считаешь именно так, вопрос «а нужен ли нам отдельный контролируемый бэкап» перестаёт быть вопросом.
Из чего на самом деле должен состоять бэкап интернет-магазина
Здесь я хочу сделать шаг назад и проговорить вещь, которая кажется очевидной, но на практике регулярно упускается: бэкап WooCommerce-магазина — это не один файл. Это как минимум два принципиально разных набора данных, которые живут в разных местах и требуют разного обращения. Первое — база данных: все ваши товары, заказы, пользователи, настройки, тексты страниц, метаданные. Второе — файлы: изображения товаров, темы, плагины, загруженные документы, кастомные шаблоны. Проблема большинства простых бэкап-решений в том, что они либо бэкапят только одно из двух, либо смешивают всё в один неструктурированный архив, из которого потом невозможно достать конкретную часть, не разворачивая весь массив целиком.
Мы в COS WP Woo сделали три отдельных типа бэкапа именно по этой причине: полный (база плюс файлы), только база данных, только файлы. Звучит как техническая деталь, но на практике разница огромная. Представьте: у вас каталог не менялся неделю, товары те же, изображения те же — а вот заказы, цены, статусы поступают постоянно. В этом случае логично бэкапить базу данных часто, хоть каждый час, а файлы — реже, раз в сутки или раз в несколько дней, потому что они тяжёлые и почти не меняются. Если у вас всё смешано в один полный бэкап, вы вынуждены либо гонять по кругу многогигабайтные архивы с фото ради того, чтобы просто иметь свежий снимок таблицы заказов, либо мириться с редкими бэкапами базы, потому что часто гонять весь массив дорого и долго.
Здесь возникает следующий практический вопрос — где всё это хранить. Хранить бэкап на том же сервере, где стоит магазин — это как хранить запасной ключ от квартиры под ковриком у той же двери: удобно, но при определённых сценариях (взлом сервера целиком, отказ диска, случайное удаление директории) бэкап погибает вместе с оригиналом. Мы храним локальные бэкапы в директории `wp-content/wpaic-backups/`, защищённой от прямого доступа через `.htaccess` с запретом на выдачу файлов и пустым `index.php` — это разумный вариант по умолчанию и хорошая страховка от простых сценариев вроде «плагин сломался, надо откатить одну таблицу». Но для серьёзной защиты копия обязана лежать физически в другом месте. Поэтому в модуле реализованы адаптеры для внешних хранилищ — Amazon S3 и совместимые с ним сервисы вроде DigitalOcean Spaces и Wasabi, Google Drive, Яндекс.Диск, Dropbox. Причём для крупных файлов реализована докачка частями — например, для S3 файлы больше ста мегабайт грузятся мультипартом кусками по пять мегабайт, чтобы не упереться в лимиты и таймауты при заливке многогигабайтного архива с фотографиями каталога.
Выбор конкретного хранилища — это не вопрос вкуса, а вопрос конкретных ограничений вашего бизнеса. Google Drive и Dropbox работают по схожей логике: небольшие файлы до пяти мегабайт у Google Drive и до полутора сотен мегабайт у Dropbox грузятся простой загрузкой в один запрос, а всё, что крупнее, — сессионно, порциями по пять и восемь мегабайт соответственно, с кэшированием токена доступа через OAuth2 refresh flow, чтобы не пере-авторизовываться каждый раз. Это удобно, если у вас уже есть корпоративный аккаунт Google Workspace и хочется держать бэкапы там же, где остальная документация компании. А вот если для вас принципиален вопрос физического расположения данных — например, вы работаете с персональными данными клиентов и хотите, чтобы копия базы данных не покидала российскую юрисдикцию, — разумный выбор это Яндекс.Диск, который создаёт папку `/wpaic-backups` автоматически и работает по REST API Яндекса без необходимости заводить американский или европейский облачный аккаунт. S3-совместимые хранилища в этом смысле универсальны: вы можете указать эндпоинт российского S3-совместимого провайдера так же легко, как эндпоинт Amazon, потому что аутентификация идёт по стандартному протоколу AWS Signature V4, а не завязана на конкретного вендора.
Расписание — вторая опора всей системы, и здесь важно не путать «есть бэкап» с «есть актуальный бэкап». Мы поддерживаем интервалы от ежечасного до еженедельного, и здесь у владельца магазина есть реальный выбор, а не фиксированный «раз в неделю, как получится». Для магазина с активными продажами разумно ставить полный бэкап раз в сутки ночью, в момент минимальной нагрузки, а бэкап только базы данных — каждые шесть часов, чтобы окно потенциальной потери заказов не превышало нескольких часов даже в худшем случае. Отдельно стоит политика хранения: по умолчанию система держит пять последних копий и автоматически удаляет самые старые при превышении лимита, но это настраиваемое число, и для магазина с оборотом в несколько сотен заказов в день я рекомендую увеличивать retention хотя бы до двух недель ежедневных копий плюс несколько еженедельных для более долгой истории. Здесь работает простое правило: чем длиннее ваша цепочка бэкапов назад во времени, тем больше шансов, что вы найдёте чистую точку восстановления даже если проблема обнаружена не сразу, а спустя дни или недели.
Механика запуска расписаний тоже стоит того, чтобы её понимать, а не воспринимать как чёрный ящик. Раз в час WP Cron поднимает проверку всех активных расписаний, сравнивает время следующего запуска с текущим моментом, и для каждого просроченного расписания ставит задачу в очередь фонового планировщика, который уже занимается непосредственно снятием копии — той же самой чанкованной обработкой, о которой я расскажу ниже. После завершения бэкапа система пересчитывает время следующего запуска и, если число хранимых копий превысило лимит retention, удаляет самые старые записи по принципу «первым пришёл — первым ушёл», причём удаление затрагивает и запись в базе, и сам файл на диске или в облаке — копии не копятся мёртвым грузом, за который вы платите провайдеру хранилища, но и не удаляются раньше срока.
Расписание — вторая опора всей системы, и здесь важно не путать «есть бэкап» с «есть актуальный бэкап». Мы поддерживаем интервалы от ежечасного до еженедельного, и здесь у владельца магазина есть реальный выбор, а не фиксированный «раз в неделю, как получится». Для магазина с активными продажами разумно ставить полный бэкап раз в сутки ночью, в момент минимальной нагрузки, а бэкап только базы данных — каждые шесть часов, чтобы окно потенциальной потери заказов не превышало нескольких часов даже в худшем случае. Отдельно стоит политика хранения: по умолчанию система держит пять последних копий и автоматически удаляет самые старые при превышении лимита, но это настраиваемое число, и для магазина с оборотом в несколько сотен заказов в день я рекомендую увеличивать retention хотя бы до двух недель ежедневных копий плюс несколько еженедельных для более долгой истории. Здесь работает простое правило: чем длиннее ваша цепочка бэкапов назад во времени, тем больше шансов, что вы найдёте чистую точку восстановления даже если проблема обнаружена не сразу, а спустя дни или недели.
Прежде чем перейти к цифрам, скажу пару слов про подключение внешних облачных хранилищ, потому что именно на этом шаге многие бросают затею на середине. Для Google Drive и Dropbox настройка идёт через OAuth2 — вы один раз проходите авторизацию через стандартное окно провайдера, система сохраняет refresh-токен и дальше продлевает доступ автоматически, без повторных запросов пароля. Access-токен при этом кэшируется на стороне сайта на ограниченное время, чтобы не дёргать API провайдера на каждый чих. Для Яндекс.Диска процесс ещё проще — вставляете OAuth-токен, полученный один раз в личном кабинете Яндекса, и указываете, если нужно, нестандартную базовую папку вместо `/wpaic-backups` по умолчанию. Для S3-совместимых хранилищ авторизация классическая — ключ доступа и секретный ключ, без интерактивного OAuth-окна вообще, что удобно для полностью автоматизированной настройки через инфраструктуру как код, если у вас в компании принят такой подход к DevOps. В любом случае весь процесс подключения занимает не больше десяти минут, и это именно тот десяток минут, который отделяет «бэкап есть только на этом же сервере» от «бэкап переживёт даже полную потерю сервера целиком».
Отдельного разговора заслуживает вопрос базы заказов на триста тысяч строк — а такая база у зрелого магазина с несколькими годами истории набегает быстро. Наивный подход «сделать дамп одним SQL-файлом и заархивировать» на такой базе имеет обыкновение упираться в лимиты выполнения скрипта на сервере, таймауты веб-сервера, лимиты памяти PHP — и в результате бэкап просто обрывается на середине, оставляя вам файл, который выглядит как полноценный дамп, но на деле битый и невосстановимый. Причём узнаёте вы об этом ровно тогда, когда пытаетесь восстановиться, то есть в худший возможный момент. Поэтому у нас длительные операции бэкапа не выполняются одним синхронным запросом — они разбиваются на чанки и выполняются в фоне через Action Scheduler, с паузой между порциями примерно в пятнадцать секунд, чтобы не создавать пиковую нагрузку на базу и не упираться в серверные лимиты времени выполнения. Для владельца магазина это выглядит как прогресс-бар, который медленно ползёт, а не как один долгий запрос, который либо завершится, либо не завершится вообще, оставив вас в неведении.
Отдельно стоит сказать про инкрементальные бэкапы — это функция, которую недооценивают, пока не столкнутся с реальными объёмами. Идея простая: вместо того чтобы каждый раз копировать все файлы целиком, система хранит манифест с контрольной суммой и временем изменения каждого файла и при следующем запуске копирует только то, что реально изменилось с прошлого раза. Для каталога с большим количеством фотографий, который меняется постепенно, это экономит огромное количество времени и места — вместо полной копии в несколько гигабайт каждую ночь вы получаете лёгкий инкремент в десятки мегабайт, а полный «якорный» бэкап делается реже, раз в неделю. При восстановлении система сначала разворачивает последний полный бэкап, а затем последовательно накатывает цепочку инкрементов до нужной точки во времени — то есть у вас появляется возможность вернуться не только «к последнему сохранённому состоянию», но и к конкретному дню внутри доступной истории.
Важный нюанс инкрементального подхода — он в принципе не имеет смысла без базового полного бэкапа, и система это знает: если инкремент запрашивается, а предыдущего полного снимка ещё не существует, она автоматически выполняет полный бэкап вместо инкремента, чтобы у вас никогда не образовывалась «висящая в воздухе» цепочка без опорной точки. Это мелочь на уровне кода, но именно такие мелочи в итоге определяют, будет ли у вас рабочая цепочка восстановления в нужный момент, или красивая с виду, но бесполезная последовательность файлов различий без якоря, от которого можно оттолкнуться.
Ежедневная рутина владельца магазина с этой системой выглядит скромно, и в этом главная ценность: заходишь в раздел бэкапов раз в неделю, видишь три цифры на приборной панели — общий размер хранимых копий, их количество, дату и статус последнего запуска — и таблицу последних снимков с пометкой, каким расписанием они созданы и в какое хранилище улетели. Если последний бэкап зелёный и дата свежая — можно спокойно заниматься остальными делами. Если статус «ошибка» — это тревожный звонок, на который стоит реагировать в тот же день, а не когда вспомнили об этом через месяц в момент аварии.
Восстановление — вот где выясняется правда о вашем бэкапе
Здесь я подхожу к тезису, который считаю самым важным во всей этой теме, и готов повторять его на каждой встрече с клиентом: бэкап, восстановление из которого никогда не проверялось, не является бэкапом. Это файл. Возможно, полезный файл, возможно, битый файл — вы не узнаете, пока не попробуете. Я видел десятки ситуаций, где компания годами исправно платила за резервное копирование, исправно получала уведомления «бэкап успешно создан», и в момент реальной аварии выяснялось, что архив повреждён, или что в него забыли включить одну критичную таблицу, или что права доступа на сервере хранения истекли ещё три месяца назад и последние бэкапы вообще никуда не долетали, просто система молча логировала ошибку, которую никто не читал.
Именно поэтому мы встроили в процесс восстановления два защитных механизма, которые кажутся избыточными до тех пор, пока не спасают вас от собственной ошибки. Первый — режим сухого прогона, dry run: система проходит по манифесту бэкапа, проверяет контрольные суммы файлов, валидирует структуру дампа базы данных, и показывает вам полный отчёт о том, что будет восстановлено, без единой фактической записи в файловую систему или в базу. Это ровно тот шаг, который позволяет обнаружить битый архив до того, как вы понадеялись на него в критической ситуации, а не после. Второй механизм — автоматический safety backup: прежде чем что-либо восстанавливать, система создаёт резервную копию текущего состояния сайта. Звучит как паранойя, но представьте ситуацию: вы восстанавливаетесь из бэкапа трёхдневной давности, чтобы вернуть удалённые товары, а выясняется, что за эти три дня пришли новые заказы, которые вы не хотели терять. Без safety backup эти три дня новых заказов просто исчезли бы безвозвратно вместе с восстановлением. С ним — вы можете аккуратно достать нужные данные из старой копии и не потерять то, что накопилось после.
Отдельно я настаиваю на регулярном тестовом восстановлении не потому что это красиво звучит в статье про бэкапы, а потому что видел цену обратного. Практика, которую я рекомендую всем клиентам — не реже раза в квартал разворачивать последний бэкап на тестовом окружении, отдельном от продакшена, и убедиться, что сайт после восстановления реально работает: открываются карточки товаров, оформляется тестовый заказ, авторизуются пользователи. Это не занимает много времени, если процесс отлажен, но именно эта практика превращает «у нас вроде есть бэкапы» в «мы точно знаем, что можем восстановиться за X минут, и уже проверяли это на практике». Разница между этими двумя состояниями — это разница между спокойным сном и катастрофой, растянутой на несколько суток паники.
Практический чек этого квартального ритуала я обычно строю в четыре шага, и ни один из них не занимает больше получаса. Сначала — разворачиваем полный бэкап на отдельном тестовом окружении, а не поверх продакшена, чтобы случайная ошибка не превратилась в реальный инцидент вместо учебного. Дальше — проходим сухим прогоном, смотрим на отчёт валидации контрольных сумм: если система жалуется хотя бы на один файл с несовпадающим sha1, это сигнал разбираться немедленно, а не откладывать. Затем — фактическое восстановление на тестовом окружении и ручная проверка трёх вещей: открывается каталог, работает оформление тестового заказа, не потерялись критичные настройки вроде способов оплаты и служб доставки. И последний шаг, который почти всегда пропускают, — замер времени всей процедуры от старта до рабочего магазина, потому что именно эта цифра ляжет в основу разговора с руководством о том, сколько реально будет стоить простой в часах, если авария случится на бою, а не на тесте.
Статусная модель самой операции тоже сделана так, чтобы не оставлять владельца в неведении: каждый бэкап проходит через состояния «ожидает выполнения», «выполняется», «завершён успешно» или «ошибка», и на каждом этапе в интерфейсе видно, на какой чанк обработки система сейчас перешла. Это не эстетическая деталь — это единственный способ отличить ситуацию «бэкап идёт медленно, потому что база большая» от ситуации «бэкап завис и больше не продвинется», не дожидаясь, пока проблема обнаружится сама собой в самый неподходящий момент.
Есть и вопрос прав доступа к самой операции восстановления, который я считаю принципиальным. Создание бэкапа в нашем модуле доступно с правом `manage_woocommerce` — то есть его может запустить любой менеджер магазина с достаточными полномочиями для управления товарами и заказами. А вот восстановление требует более строгого права `manage_options`, которое обычно есть только у администратора сайта. Это осознанное архитектурное решение: создание копии — операция низкого риска, а вот перезапись боевой базы данных чужим состоянием — операция, которая должна требовать более высокого уровня доверия. Плюс на уровне путей файловой системы восстановление работает по принципу белого списка — операция физически не может выйти за пределы директории `wp-content`, что закрывает целый класс потенциальных проблем, если бы кто-то попытался подсунуть архив с вредоносным путём внутри.
Бэкап — не индульгенция: история с майнером
Хочу рассказать одну поучительную, хоть и обобщённую историю, потому что она хорошо иллюстрирует ловушку мышления, в которую попадают даже те, кто ответственно относится к резервному копированию. Один клиент — небольшой интернет-магазин промышленного оборудования — обнаружил, что сервер начал вести себя странно: нагрузка процессора держится на восьмидесяти процентах круглые сутки, хотя посещаемость сайта не выросла ни на йоту. Разобрались быстро: на сервере оказался майнер криптовалюты, подсаженный через уязвимость в давно не обновлявшемся стороннем плагине. Первая реакция владельца была абсолютно логичной: «У нас есть бэкап недельной давности, до того как это началось, — разворачиваем его, и порядок». Разворачивают. Сервер снова спокоен полтора часа. А затем нагрузка опять начинает расти.
Что произошло? Восстановление файлов и базы данных откатило видимые последствия — сам файл майнера, изменённые записи в базе, подозрительные строки в конфигурации. Но оно не закрыло саму дыру, через которую злоумышленник изначально попал внутрь. Уязвимый плагин остался тем же уязвимым плагином, потому что бэкап, из которого восстанавливались, был снят уже после того, как дыра появилась в системе — она успела появиться задолго до того, как её начали эксплуатировать. Атакующий, скорее всего автоматизированный скрипт, просканировал сеть заново, нашёл ту же самую открытую дверь и вошёл повторно, установив майнер во второй раз. Восстановление из бэкапа в этой ситуации сработало как обезболивающее при переломе — сняло симптом, но не тронуло причину.
Это подводит меня к мысли, которую я считаю критически важной и которую редко проговаривают в материалах про резервное копирование: бэкап — это инструмент восстановления состояния, а не инструмент устранения причины проблемы. После любого инцидента безопасности восстановление из бэкапа должно идти рука об руку с обновлением уязвимого компонента, сменой всех паролей и API-ключей, которые теоретически могли быть скомпрометированы, и проверкой на предмет свежих бэкдоров, которые могли появиться уже после момента, из которого вы восстанавливаетесь. Именно поэтому бэкап-модуль у нас работает не изолированно, а в связке с остальной системой безопасности — тем же WAF, который блокирует известные паттерны эксплуатации уязвимостей, мониторингом целостности файлов, который замечает появление нового PHP-файла там, где ему быть не положено, и логом активности, который показывает, кто и когда что-то менял в системе. Восстановиться без параллельного закрытия уязвимости — это гарантия, что вы встретитесь с той же проблемой снова, возможно, уже без запаса терпения и без желания разбираться во второй раз.
Здесь же стоит упомянуть паттерн, который прекрасно накладывается на историю с брутфорсом и ботами, о котором я подробно писал в статье про защиту от брутфорса без Wordfence: скомпрометированный административный аккаунт — это не только риск утечки данных, но и прямой путь к загрузке вредоносных файлов на сервер. Если у вас нет двухфакторной аутентификации и защиты от подбора пароля, вероятность повторной компрометации после восстановления резко возрастает, потому что вектор атаки остаётся открытым независимо от того, насколько свежий у вас бэкап.
Из этой истории я вынес для себя простое правило, которое теперь проговариваю каждому клиенту после любого инцидента: восстановление из бэкапа закрывает вопрос «как быстро мы вернулись к работе», но не закрывает вопрос «как мы теперь не допустим повторения». Это два разных проекта, и второй обычно требует немного больше, чем один клик в админке — обновления всех устаревших плагинов и тем, смены паролей у всех пользователей с доступом к админке, перевыпуска API-ключей платёжных систем и интеграций, и хотя бы поверхностного аудита файлов на предмет посторонних PHP-скриптов, которые могли появиться уже после точки восстановления. Занимает это обычно один рабочий день специалиста. Не заниматься этим вообще — значит подписаться на второй раунд той же самой истории, возможно, в куда менее удобный момент.
Сколько это стоит, и почему UpdraftPlus — не то же самое
Раз уж мы заговорили о деньгах и рисках, стоит честно сравнить наш подход с самым популярным независимым решением для бэкапов на WordPress — UpdraftPlus. Это хороший, зрелый плагин с миллионами установок, и я не буду делать вид, что он плохой. Но у него есть особенности, о которых стоит знать до того, как полагаться на него как на единственную линию защиты. Бесплатная версия UpdraftPlus не умеет инкрементальные бэкапы, не имеет встроенной проверки целостности через контрольные суммы, а восстановление больших баз данных на слабом хостинге регулярно упирается в те же самые таймауты, о которых я говорил выше, потому что архитектурно это тоже полноценный отдельный плагин со своей логикой, не интегрированной с остальным магазином. Платная версия снимает часть ограничений, но добавляет отдельную статью расходов — обычно от 70 долларов в год за один сайт, и это поверх того, что вы уже платите за остальные функции магазина: безопасность, доставку, интеграцию с 1С, поиск. Плюс это ещё один плагин в списке, ещё одна точка потенциального конфликта с кешированием, ещё одно обновление, за которым нужно следить.
Наша логика с самого начала была другой: бэкап — это не опциональная надстройка, которую вы прикручиваете отдельно и молитесь, что она подружится с остальным магазином. Это часть единой системы COS WP Woo, которая уже знает структуру ваших данных, уже интегрирована с планировщиком фоновых задач, который используется и для синхронизации с 1С, и для переиндексации поиска, и для остальных длительных операций. Когда вы восстанавливаете магазин, система понимает, что перед этим стоит сделать safety backup, что нужно проверить целостность архива, что операция требует более строгих прав доступа — потому что весь этот контекст уже встроен в архитектуру, а не прикручен сбоку через хуки к чужому плагину.
Если сравнивать стоимость владения на более длинной дистанции, картина становится ещё интереснее. Отдельный бэкап-плагин премиум-класса, отдельный плагин безопасности премиум-класса, отдельный плагин для B2B, отдельный плагин для доставки — за несколько лет владения магазином это выливается в серьёзную сумму, о которой я подробно рассказывал в обзоре «Один плагин вместо двадцати»: зоопарк расширений не только стоит денег напрямую, но и создаёт риски совместимости, каждый из которых теоретически может стать тем самым уязвимым звеном, из-за которого вам вообще понадобится восстанавливаться из бэкапа.
Есть ещё одна статья расходов, о которой почти никогда не вспоминают заранее, — сама облачная инфраструктура хранения. Гигабайт на S3 или в Google Drive стоит копейки, и это правда, пока речь не идёт о ежедневных полных бэкапах магазина с десятками тысяч фотографий товаров и retention в несколько недель. Умножьте средний размер полного бэкапа на число хранимых копий, и вы получите не абстрактную, а вполне конкретную ежемесячную строчку в бюджете облачного хранилища. Инкрементальные бэкапы здесь не просто техническое изящество — это прямая экономия денег, потому что вместо N полных копий вы платите за одну полную плюс N лёгких инкрементов, и разница в счёте за хранилище к концу года может быть в разы.
Я прекрасно понимаю, что тема резервного копирования звучит скучно. Она не про рост продаж, не про красивый дизайн, не про хитрый маркетинговый трюк, который увеличит конверсию на пятнадцать процентов. Она про то, что происходит в тот момент, когда всё остальное уже не имеет значения — потому что сайт лежит, база повреждена, а клиенты не могут оформить заказ. И вот в этот момент единственное, что действительно важно, — это ответ на простой вопрос: у вас есть проверенный, актуальный, восстановимый бэкап, или у вас есть файл, о котором вы надеетесь, что он окажется бэкапом. Я видел обе версии этой истории десятки раз, и разница между ними — это разница между «неприятным утром» и «неделей, вычеркнутой из жизни бизнеса».
И последнее наблюдение, которым хочу закрыть эту тему. За годы работы с самыми разными магазинами я заметил забавную закономерность: владельцы, которые один раз пережили серьёзную потерю данных и восстановились без нормального бэкапа — вручную, по крупицам, через выгрузки из почты и скриншоты из аналитики, — после этого относятся к резервному копированию как к чему-то само собой разумеющемуся, вроде страховки на автомобиль. А те, кому пока везло, продолжают откладывать настройку на «как-нибудь потом». Разница между этими двумя группами не в уме и не в опыте ведения бизнеса — просто одни уже заплатили цену урока, а другие ещё нет. Хорошая новость в том, что этот урок не обязательно проходить на собственной шкуре — можно один раз потратить полчаса на настройку и больше никогда не оказаться в этой истории.
Если вы до сих пор полагаетесь исключительно на бэкап от хостинга — потратьте полчаса и проверьте на практике, за какой период доступна копия, включает ли она базу данных целиком, и главное — попробуйте её восстановить хотя бы на тестовом окружении. Если результат вас не устроит, в COS WP Woo модуль резервного копирования настраивается за пятнадцать минут: расписание, выбор внешнего хранилища, тестовое восстановление — и вы будете точно знать, что произойдёт в тот день, когда бэкап действительно понадобится, а не гадать об этом постфактум.
