Хостинг

Как настроить ежедневные резервные копии

Как настроить ежедневные копии сайта и проверить восстановление: хостинг, VPS, независимое хранение и полный пример для Ubuntu с Nginx и SQLite.

Сервер передаёт копии данных в два независимых хранилища

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

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

Разберём встроенные копии хостинга, сценарий для VPS, хранение версий, уведомления и проверку восстановления. Общая схема подходит для сайтов с файлами и базой данных. Ниже есть полный практический пример для Ubuntu, Nginx и учебной SQLite: создание архива, ежедневный таймер и проверка восстановления.

Сначала определите, что нельзя потерять

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

Что сохранять

Примеры

Что проверить

Файлы приложения

CMS, темы, собственный код

Можно ли воспроизвести версию из репозитория

Пользовательские данные

Фото, документы, вложения

Входят ли все каталоги загрузок

База

Публикации, заказы, настройки

Согласованность и полнота дампа

Конфигурация

Пути, переменные окружения, задания

Защищены ли секреты и описано ли восстановление

Почта

Ящики и важные вложения

Входит ли в копию хостинга или хранится отдельно

Внешнее хранилище

Медиа в S3-совместимом сервисе

Есть ли независимая версия или копия объектов

Не копируйте только то, что видно в файловом менеджере. База MySQL обычно не находится в папке сайта в виде готового SQL-файла. А резервная копия VPS у провайдера может не включать внешний почтовый сервис.

Для WordPress состав копии поясняется в официальном руководстве. На практике дополните его собственным списком интеграций и мест хранения данных.

Ежедневно — не всегда достаточно

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

Есть два полезных вопроса: сколько новых данных можно потерять и сколько времени сайт может оставаться недоступным. Их часто обозначают RPO и RTO. Названия не обязательны, но сами ответы нужны до выбора схемы.

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

Не обещайте «восстановление за пять минут», если полный архив скачивается час. Проверьте фактическое время передачи, распаковки, импорта базы, выпуска сертификата и проверки функций. Для интернет-магазина это часть требований к инфраструктуре.

Вариант 1. Встроенные копии виртуального хостинга

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

Сделайте пробное восстановление в отдельный каталог или тестовый сайт, если панель это позволяет. Не нажимайте «восстановить всё» на рабочем проекте без понимания, какие свежие данные будут заменены.

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

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

Вариант 2. Расширение CMS

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

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

Планировщик, который запускается только при посещении сайта, не гарантирует точное выполнение ночью на малопосещаемом проекте. Если CMS поддерживает системный cron, настройте его по официальной инструкции и убедитесь, что задачи не дублируются.

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

Вариант 3. Управляемый процесс на VPS

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

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

Не начинайте с удаления старых версий. Сначала добейтесь нескольких успешных копий и пробного восстановления. Политика очистки должна применяться к уже проверенной системе, а не к единственному архиву, который вы ещё не открывали.

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

Как получить согласованную копию базы

Простое копирование файлов работающей MySQL из её каталога данных не равно корректному резервному копированию. Используйте дамп, поддерживаемый физический backup-инструмент или штатную функцию управляемой СУБД.

Для транзакционных таблиц типовой пример дампа:

mysqldump --defaults-extra-file=/private/mysql-backup.cnf \
  --single-transaction --quick --no-tablespaces \
  site_db > /private/staging/site.sql

Файл параметров должен быть закрыт от посторонних, а каталог staging — находиться вне публичной папки. Для автоматизации не записывайте пароль в командную строку. Пример не является готовой настройкой для любой СУБД: имена, права и опции подбираются под вашу версию и схему.

Во время дампа не выполняйте изменения структуры таблиц. Для нетранзакционных таблиц и совместной согласованности с файлами нужен отдельный порядок блокировки записи или специализированное решение.

Если приложение сохраняет файл, а затем запись о нём в базе, независимые снимки в разные моменты могут расходиться. Для строгой согласованности используйте предусмотренный приложением режим паузы записи или согласованный snapshot-процесс. У активного магазина это важнее, чем формальное наличие ежедневного архива.

Практика на Ubuntu: ежедневная копия Nginx и SQLite

Ниже — полный пример для Ubuntu 24.04 LTS из нашей серии о первом VPS. Сохраняем статический сайт из урока по Nginx, его виртуальный хост и небольшую учебную базу SQLite. Затем проверяем восстановление в отдельном каталоге.

Этот пример не требует устанавливать сервер базы данных: модуль sqlite3 входит в Python Ubuntu. Если ваш сайт использует MySQL или PostgreSQL, замените часть получения базы подходящим согласованным дампом; создание тестовой SQLite не копирует вашу настоящую БД.

1. Определяем состав копии

Источник Что попадёт в архив
/srv/hostfera-lab/web/public/ Статические файлы учебного сайта
/etc/nginx/sites-available/hostfera-lab.conf Конфигурация виртуального хоста
/srv/hostfera-lab/backup-demo/demo.sqlite3 Согласованная копия учебной SQLite
Сформированные служебные файлы Дата, описание состава и SHA-256 каждого файла

Архивы лежат в /var/backups/hostfera-lab/, вне веб-каталога. Права каталога — 700, файлов архивов — 600. TLS-ключи и сертификаты в этот пример не включены: для них нужен отдельный план хранения или повторного выпуска, как и для внешних сервисов приложения.

Административные команды далее выполняются в оболочке root. Из учётной записи с sudo её можно открыть командой sudo -i. Перед созданием файлов убедитесь, что указанные пути относятся к вашему учебному сайту, а одноимённых служебных файлов ещё нет.

Создайте каталоги:

install -d -m 700 /var/backups/hostfera-lab
install -d -m 700 /srv/hostfera-lab/backup-demo

Учебная база содержит две записи. Она создаётся отдельно от каталога сайта и не публикуется через Nginx:

test ! -e /srv/hostfera-lab/backup-demo/demo.sqlite3 && python3 - <<'PY'
import sqlite3
from pathlib import Path

path = Path('/srv/hostfera-lab/backup-demo/demo.sqlite3')
with sqlite3.connect(path) as db:
    db.execute('CREATE TABLE notes (id INTEGER PRIMARY KEY, title TEXT NOT NULL)')
    db.executemany('INSERT INTO notes(title) VALUES (?)', [
        ('Hostfera backup demo',),
        ('Restore verified',),
    ])
path.chmod(0o600)
PY

Если файл уже существует, сначала выясните его назначение. Не заменяйте рабочую базу учебным примером.

2. Устанавливаем скрипт копирования

Создайте /usr/local/sbin/hostfera-lab-backup и вставьте код:

#!/usr/bin/python3
"""Backup this lesson's static webroot, vhost and demo SQLite database."""
import fcntl
import hashlib
import json
import os
import re
import shutil
import sqlite3
import tarfile
import tempfile
import uuid
from datetime import datetime, timezone
from pathlib import Path

SITE = Path('/srv/hostfera-lab/web/public')
CONFIG = Path('/etc/nginx/sites-available/hostfera-lab.conf')
DATABASE = Path('/srv/hostfera-lab/backup-demo/demo.sqlite3')
DESTINATION = Path('/var/backups/hostfera-lab')
KEEP = 14
PATTERN = re.compile(r'hostfera-lab-\d{8}T\d{6}Z-[0-9a-f]{8}\.tar\.gz')

def digest(path):
    with path.open('rb') as stream:
        return hashlib.file_digest(stream, 'sha256').hexdigest()

def main():
    os.umask(0o077)
    if not SITE.is_dir() or SITE.is_symlink():
        raise RuntimeError('The expected static site directory is missing')
    for path in (CONFIG, DATABASE):
        if not path.is_file() or path.is_symlink():
            raise RuntimeError('Missing or symbolic source: ' + str(path))
    # The lesson uses an unchanging static webroot; do not follow external links.
    for path in SITE.rglob('*'):
        if path.is_symlink() or not (path.is_file() or path.is_dir()):
            raise RuntimeError('Unsupported site entry: ' + str(path))
    DESTINATION.mkdir(mode=0o700, parents=True, exist_ok=True)
    if DESTINATION.is_symlink():
        raise RuntimeError('The backup directory must not be a symlink')
    with Path('/run/lock/hostfera-lab-backup.lock').open('a') as lock:
        fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
        stamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')
        name = f'hostfera-lab-{stamp}-{uuid.uuid4().hex[:8]}.tar.gz'
        partial = DESTINATION / ('.' + name + '.part')
        checksum_partial = DESTINATION / ('.' + name + '.sha256.part')
        try:
            with tempfile.TemporaryDirectory(prefix='.staging-', dir=DESTINATION) as work:
                stage = Path(work)
                shutil.copytree(SITE, stage / 'site')
                (stage / 'config').mkdir()
                shutil.copy2(CONFIG, stage / 'config' / CONFIG.name)
                (stage / 'database').mkdir()
                with sqlite3.connect(DATABASE.as_uri() + '?mode=ro', uri=True) as source:
                    with sqlite3.connect(stage / 'database' / 'demo.sqlite3') as target:
                        source.backup(target)
                        if target.execute('PRAGMA integrity_check').fetchone()[0] != 'ok':
                            raise RuntimeError('The SQLite copy failed integrity_check')
                (stage / 'metadata.json').write_text(json.dumps({
                    'created_utc': stamp, 'scope': 'Hostfera static site and demo SQLite',
                    'tls_private_key_included': False,
                }, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
                members = sorted(p for p in stage.rglob('*') if p.is_file())
                manifest = ''.join(digest(p) + '  ' + p.relative_to(stage).as_posix() + '\n' for p in members)
                (stage / 'SHA256SUMS').write_text(manifest, encoding='utf-8')
                with tarfile.open(partial, 'w:gz') as archive:
                    for path in sorted(stage.iterdir()):
                        archive.add(path, arcname=path.name)
                # Read every file again before publishing or rotating copies.
                expected = {p.relative_to(stage).as_posix(): digest(p) for p in members}
                with tarfile.open(partial, 'r:gz') as archive:
                    observed = {}
                    for entry in archive.getmembers():
                        if entry.isfile() and entry.name != 'SHA256SUMS':
                            with archive.extractfile(entry) as stream:
                                observed[entry.name] = hashlib.file_digest(stream, 'sha256').hexdigest()
                    if observed != expected:
                        raise RuntimeError('The archive differs from its manifest')
                checksum_partial.write_text(digest(partial) + '  ' + name + '\n', encoding='utf-8')
                os.replace(partial, DESTINATION / name)
                os.replace(checksum_partial, DESTINATION / (name + '.sha256'))
                copies = sorted(p for p in DESTINATION.iterdir() if PATTERN.fullmatch(p.name) and p.is_file() and not p.is_symlink())
                for old in copies[:-KEEP]:
                    old.unlink()
                    old.with_name(old.name + '.sha256').unlink(missing_ok=True)
                print('Archive:', name)
                print('Files verified:', len(members))
                print('SQLite integrity_check: ok')
                print('Stored versions:', min(len(copies), KEEP))
        finally:
            partial.unlink(missing_ok=True)
            checksum_partial.unlink(missing_ok=True)

if __name__ == '__main__':
    main()

Задайте владельца и права:

chown root:root /usr/local/sbin/hostfera-lab-backup
chmod 750 /usr/local/sbin/hostfera-lab-backup

Скрипт не копирует живую SQLite простым cp: метод backup() создаёт согласованную копию через API базы. Для файлов используется отдельный staging-каталог. Сначала проверяется содержимое нового архива, затем он появляется под окончательным именем, после чего удаляются только собственные старые версии.

Значение KEEP = 14 означает последние 14 успешных архивов. Это не всегда 14 суток: ручные дополнительные запуски тоже создают версии. Блокировка защищает от одновременного выполнения двух экземпляров.

Статический сайт в примере не меняется во время копирования. Для приложения, которое одновременно записывает файлы и базу, отдельно обеспечьте согласованность всего проекта: например, предусмотренным приложением режимом обслуживания или согласованным snapshot. API SQLite обеспечивает согласованность базы, а не всех внешних файлов приложения.

3. Создаём службу и проверяем ручной запуск

Файл /etc/systemd/system/hostfera-lab-backup.service:

[Unit]
Description=Hostfera lesson backup of static site and demo SQLite

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/hostfera-lab-backup
User=root
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/backups/hostfera-lab /run/lock
TimeoutStartSec=10min

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

Файл /etc/systemd/system/hostfera-lab-backup.timer:

[Unit]
Description=Daily Hostfera lesson backup

[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true
RandomizedDelaySec=5min
Unit=hostfera-lab-backup.service

[Install]
WantedBy=timers.target

Проверьте определения и запустите копирование:

systemd-analyze verify /etc/systemd/system/hostfera-lab-backup.service /etc/systemd/system/hostfera-lab-backup.timer
systemctl daemon-reload
systemctl start hostfera-lab-backup.service
systemctl show hostfera-lab-backup.service -p Result -p ExecMainStatus
journalctl -u hostfera-lab-backup.service -n 8 --no-pager -o cat

Ожидаются Result=success, ExecMainStatus=0, имя созданного архива и SQLite integrity_check: ok. Служба типа oneshot после успешного завершения может иметь состояние inactive: это не ошибка само по себе. Оценивайте результат последнего запуска и журнал.

Успешная служба резервного копирования, имя архива и SQLite integrity_check ok
Архив создан, служба завершилась успешно, копия SQLite прошла проверку.

4. Включаем ежедневный таймер

timedatectl show -p Timezone
systemctl enable --now hostfera-lab-backup.timer
systemctl list-timers hostfera-lab-backup.timer --no-pager

Расписание задано на 03:15 по часовому поясу сервера, с дополнительной случайной задержкой до пяти минут. Persistent=true позволяет выполнить пропущенное календарное задание после возвращения сервера к работе. Конкретное ближайшее время смотрите в list-timers.

Чтобы проверить запуск планировщиком, не ожидая следующего дня, можно создать отдельный временный таймер для той же службы:

systemd-run --unit=hostfera-lab-backup-proof --on-active=8s --timer-property=AccuracySec=1s /usr/bin/systemctl start hostfera-lab-backup.service

Через несколько секунд проверьте новый архив и результат службы, затем остановите только проверочный таймер:

systemctl show hostfera-lab-backup.service -p Result -p ExecMainStatus
ls -lh /var/backups/hostfera-lab/
systemctl stop hostfera-lab-backup-proof.timer

Основной ежедневный таймер hostfera-lab-backup.timer должен остаться активным. Не путайте его с временной проверкой.

Часовой пояс сервера, включённый systemd timer, время следующего запуска и успешный результат службы
Ежедневный таймер активен; дополнительный запуск планировщиком проверен.

5. Проверяем архив и сохраняем копию отдельно

В каталоге копий выберите конкретный архив. В командах ниже переменная получает имя последнего архива нашего примера:

cd /var/backups/hostfera-lab
archive=$(find . -maxdepth 1 -type f -name 'hostfera-lab-*.tar.gz' -printf '%f\n' | sort | tail -1)
test -n "$archive"
sha256sum -c "$archive.sha256"
tar -tzf "$archive"

Ожидается OK. В составе должны быть site/, config/, database/, metadata.json и SHA256SUMS.

SHA-256 архива OK, состав резервной копии, права 700 каталога и 600 архива
Контрольная сумма совпала; в архиве есть сайт, конфигурация, база и манифест.

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

Например, на своём компьютере можно скачать выбранный архив и его контрольную сумму через scp. Замените IP и имя файла своими:

archive='ИМЯ_СОЗДАННОГО_АРХИВА.tar.gz'
scp "root@203.0.113.10:/var/backups/hostfera-lab/$archive" .
scp "root@203.0.113.10:/var/backups/hostfera-lab/$archive.sha256" .
sha256sum -c "$archive.sha256"

В Windows PowerShell файл можно проверить через Get-FileHash -Algorithm SHA256. Сравните полученное значение с первой колонкой файла .sha256. Учтите, что локальные и серверные имена пользователей могут различаться.

SHA-256 обнаруживает изменение данных, но не шифрует архив и не подтверждает его автора, если злоумышленник заменил и сам архив, и файл сумм. Важны доверенный канал передачи, защищённое хранилище и отдельные права доступа.

Расписание и часовой пояс

Выберите время низкой нагрузки и проверьте часовой пояс планировщика. Если сервер использует UTC, ночное по московскому времени задание нужно рассчитать соответствующим образом.

Пример записи cron после того, как скрипт уже подготовлен и проверен:

17 2 * * * /usr/bin/flock -n /private/backup/site.lock /private/backup/site-backup.sh >> /private/backup/run.log 2>&1

Это пример ежедневного запуска в 02:17 по времени cron, а не обещание готовой установки. Все каталоги, права и пути к командам должны существовать. Журнал и lock-файл должны быть доступны только соответствующей служебной учётной записи.

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

На VPS можно использовать systemd timer с журналированием и обработкой отказов. Какой бы механизм вы ни выбрали, задайте один источник расписания, чтобы cron и timer не запускали одну и ту же задачу одновременно.

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

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

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

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

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

Сколько версий сохранять

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

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

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

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

Как проверять каждую копию

Минимум — код завершения, время последнего успеха, наличие нужных объектов и правдоподобный объём. Резкое уменьшение архива может означать пропавший каталог или пустой дамп, а резкий рост — включение старых резервных копий.

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

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

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

Как провести пробное восстановление

Восстановление проверяют в отдельном каталоге. Для нашего примера это /srv/hostfera-lab/restore-check/: рабочий сайт и его данные при распаковке не заменяются. Сначала убедитесь, что выбранный архив прошёл проверку внешней контрольной суммы, как показано выше.

1. Распаковываем доверенный архив в новый каталог

Продолжая работу в /var/backups/hostfera-lab/ с переменной archive, выполните:

install -d -m 700 /srv/hostfera-lab/restore-check
restore="/srv/hostfera-lab/restore-check/${archive%.tar.gz}"
python3 - "$archive" "$restore" <<'PY'
import sys
import tarfile
from pathlib import Path, PurePosixPath

source = Path(sys.argv[1])
destination = Path(sys.argv[2])
assert destination.parent == Path('/srv/hostfera-lab/restore-check')
assert not destination.exists(), 'Choose a new restore directory'
destination.mkdir(mode=0o700)
with tarfile.open(source, 'r:gz') as bundle:
    for item in bundle.getmembers():
        path = PurePosixPath(item.name)
        assert not path.is_absolute() and '..' not in path.parts
        assert item.isfile() or item.isdir()
        assert path.parts[0] in ('site', 'config', 'database', 'metadata.json', 'SHA256SUMS')
    bundle.extractall(destination, filter='data')
print(destination)
PY

Пример рассчитан на Python 3.12 из Ubuntu 24.04. Он отбрасывает неожиданные типы записей и пути, а filter='data' ограничивает опасные свойства распаковки. Не распаковывайте неизвестные архивы под root; защитные фильтры не превращают произвольный архив в доверенный.

2. Сверяем файлы и содержимое базы

cd "$restore"
sha256sum -c SHA256SUMS
python3 - <<'PY'
import sqlite3

with sqlite3.connect('file:database/demo.sqlite3?mode=ro', uri=True) as db:
    print('SQLite:', db.execute('PRAGMA integrity_check').fetchone()[0])
    print('Rows:', db.execute('SELECT count(*) FROM notes').fetchone()[0])
PY

Для подготовленной учебной базы ожидаются SQLite: ok и Rows: 2. Для своего приложения дополнительно проверьте важные записи, связи и операции. Успешная проверка структуры базы не заменяет проверку её актуальности и полноты.

Проверка SHA256SUMS после распаковки, SQLite ok и Rows 2
Восстановленные файлы совпадают с манифестом, в SQLite вернулись две записи.

3. Убеждаемся, что проверка замечает повреждение

Можно проверить сам контроль целостности: измените только восстановленную учебную копию, затем повторите проверку.

printf '\nchanged-for-restore-test\n' >> site/index.html
sha256sum -c SHA256SUMS

Для site/index.html должен появиться FAILED, а команда завершится с ненулевым кодом. Затем верните этот файл из того же проверенного архива:

tar -xzf /var/backups/hostfera-lab/"$archive" -C "$restore" site/index.html
sha256sum -c SHA256SUMS

После восстановления снова ожидаются строки OK.

SHA256SUMS показывает FAILED и код 1 для изменённого файла, затем OK после восстановления
Изменённый файл обнаружен; после возврата из архива проверка снова проходит.

4. Открываем восстановленный сайт через отдельный Nginx-хост

Для статического сайта можно создать временный виртуальный хост только на loopback-адресе. Публичные порты и DNS для этой проверки не нужны.

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

ss -lnt 'sport = :8081'
test ! -e /etc/nginx/conf.d/hostfera-lab-restore-proof.conf
chmod 711 /srv/hostfera-lab/restore-check "$restore"

Создайте конфигурацию. В этом блоке $restore подставляется оболочкой, а экранированные \$uri должны попасть в конфигурацию как переменные Nginx:

cat > /etc/nginx/conf.d/hostfera-lab-restore-proof.conf <<EOF
server {
    listen 127.0.0.1:8081;
    server_name _;
    root $restore/site;
    index index.html;
    charset utf-8;
    add_header X-Robots-Tag "noindex, nofollow" always;
    location / { try_files \$uri \$uri/ =404; }
    location ~ /\. { deny all; }
}
EOF
nginx -t && systemctl reload nginx

Проверьте ответ и его совпадение с восстановленным файлом:

curl -fsS -o /dev/null -w 'Restored HTTP status: %{http_code}\n' http://127.0.0.1:8081/
curl -fsS http://127.0.0.1:8081/ | sha256sum
sha256sum site/index.html

Ожидается 200, а две контрольные суммы должны совпасть. Проверьте и основной сайт через его HTTPS-адрес.

Восстановленная страница Nginx отвечает HTTP 200, совпадающие SHA-256 и основной HTTPS 200
Восстановленный сайт отвечает через Nginx, суммы страницы и файла совпадают.

5. Завершаем проверку

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

rm -- /etc/nginx/conf.d/hostfera-lab-restore-proof.conf
nginx -t && systemctl reload nginx

Сохраните отчёт: какой архив восстановлен, прошли ли контрольные суммы, сколько записей вернулось в базу, запустился ли сайт и сколько заняло восстановление. В учебном примере временный веб-хост после проверки убран, а ежедневный таймер копирования оставлен активным.

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

Почему snapshot провайдера не заменяет весь план

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

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

Не используйте откат всего сервера как первое действие при повреждении одной картинки. Восстановление должно соответствовать задаче: файл, база или весь проект. Масштабный откат способен удалить более свежие заказы и настройки.

Что делать при неудачном ежедневном запуске

Сначала проверьте, есть ли пригодная предыдущая копия и насколько она старая. Затем выясните этап отказа: дамп, чтение файлов, передача, доступ к хранилищу или проверка.

Частые причины — переполненная квота, изменённый пароль, недоступный внешний сервис, истёкший токен и зависшее предыдущее задание. Не удаляйте старые версии, чтобы «освободить место», пока не проверили последствия и резервный путь восстановления.

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

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

Пример схемы для небольшого блога

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

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

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

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

Пример для нескольких клиентских сайтов

Если один аккаунт содержит проекты разных клиентов, общий архив усложняет доступ и выборочное восстановление. Подумайте, кому разрешено видеть данные и кто должен получать уведомления по каждому сайту.

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

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

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

Что должно быть в инструкции восстановления

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

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

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

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

Проверка устойчивости к ошибкам доступа

Представьте, что пароль основного сервера утрачен или аккаунт провайдера временно недоступен. Можно ли получить независимую копию и развернуть её на другой площадке? Если нет, схема всё ещё зависит от единственной точки управления.

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

Убедитесь, что смена токена хранилища сопровождается тестом следующего запуска. Старый ключ мог оставаться в cron, переменных службы или панели расширения. Успешное ручное подключение новым ключом не доказывает, что расписание использует его же.

Стоимость восстановления тоже нужно учитывать

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

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

Сравните стоимость с допустимым простоем. Более дешёвое архивное хранение может иметь другую доступность и скорость выдачи. Выбор зависит от того, нужно ли вернуть сайт срочно или допустимо ждать восстановления архива.

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

Можно ли хранить только последнюю копию?

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

Нужно ли сохранять кеш?

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

Достаточно ли копии базы?

Для CMS с изображениями и документами — нет. Нужны связанные файлы и настройки, а также понятный способ развернуть совместимое окружение.

Почему копия успешна, а сайт после восстановления не работает?

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

Когда делать внеплановую копию?

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

Проверка готовой системы

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

  • Копируются файлы и согласованная база.

  • Есть внешнее защищённое хранилище и отдельный доступ.

  • Расписание не создаёт параллельные запуски.

  • Проверяются результат, возраст последнего успеха и состав.

  • Сохраняется история, очистка выполняется по понятной политике.

  • Оповещения об отказе испытаны.

  • Восстановление выполнено на отдельной площадке и измерено по времени.

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

Данные приложений в Docker

Если запускаете приложение через Docker Compose, включите его тома и базы в отдельный план копирования. Именованный том переживает пересоздание контейнера, но не заменяет независимую резервную копию. Приведённый выше учебный таймер не копирует Docker-том автоматически.

Где держать независимую копию

Если выбираете отдельное место для архивов, прочитайте обзор объектного хранилища S3. Он помогает оценить доступ, объём, трафик и ограничения; автоматическая отправка копий настраивается отдельно.

Отдельная копия PostgreSQL

Для базы PostgreSQL используйте проверенный скрипт pg_dump и восстановление в пустую базу. Копирование файлов работающей СУБД обычным архиватором не заменяет резервирование базы.

Первоисточники

Нашли неточность?

Сообщите адрес статьи и фрагмент через контакты редакции.