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

Почему rsync остаётся главным инструментом копирования файлов

rsync появился в 1996 году и с тех пор не имеет себе равных в своей нише. Секрет в дельта-алгоритме: при повторном запуске утилита сравнивает файлы источника и приёмника по размеру и времени модификации и передаёт по сети только изменившиеся части. Если в гигабайтском каталоге поменялась одна страница, по сети уйдут килобайты. Транспорт работает поверх SSH, поэтому копия шифруется в пути без отдельной настройки.

Флаг -a это сборный режим, короткая запись сразу нескольких полезных опций:

  1. -r, рекурсивный обход каталогов;
  2. -l, символические ссылки копируются как ссылки;
  3. -p, сохранение прав доступа;
  4. -t, сохранение времён модификации;
  5. -g, сохранение группы файла;
  6. -o, сохранение владельца, эффект проявляется под root;
  7. -D, устройства и специальные файлы.

Сравните альтернативы: cp не умеет сеть вообще, tar со сжатием гоняет весь архив заново при каждом запуске, файловые менеджеры по FTP теряют права и требуют ручного внимания. rsync закрывает всё одним вызовом, работает по SSH без агентов на приёмнике и возвращает понятный код ошибки, если что-то пошло не так. Ещё одна спасательная способность, это продолжение прерванной передачи: с флагом --partial утилита не выбрасывает недокачанное, и сорвавшийся на девяноста процентах перенос не начинается заново. Для архивов на сотни гигабайтов это свойство ценнее скорости.

Ключи SSH для копирования без пароля на резервный сервер

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

ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""
ssh-copy-id -i /root/.ssh/backup_key.pub Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
ssh -i /root/.ssh/backup_key Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. "echo ok"

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

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

Команда rsync для полной копии сайта вместе с базой данных

База данных не файл, скопировать её простым копированием каталога нельзя: пока rsync читает файлы, в них пишутся новые данные, и копия получится битой. Поэтому база сначала выгружается в дамп, потом дамп уезжает вместе с файлами. Пароль для mysqldump хранится в /root/.my.cnf, чтобы не светить его в процессах и скриптах:

[client]
user=root
password="СЛОЖНЫЙ_ПАРОЛЬ"

Файл закрывается правами chmod 600 /root/.my.cnf. Сама выгрузка базы и копирование файлов выглядят так:

mysqldump --defaults-extra-file=/root/.my.cnf --single-transaction site_db | gzip > /var/backups/site/db.sql.gz
rsync -az --delete /var/www/site/ Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.:/backups/site/files/

Ключ --single-transaction снимает согласованное состояние InnoDB без блокировки записи: сайт продолжает работать, а дамп получается целостным. Флаг -z включает сжатие в канале, на медленном интернете это заметная экономия. Для сайтов на WordPress логика та же: база уходит дампом, каталог тем и загрузок копируется файлами, а само ядро при восстановлении ставится из свежего дистрибутива за минуты. Правило простое: копируется то, что нельзя пересоздать, остальное восстанавливается переустановкой.

Один нюанс, на который спотыкаются все новички: слэш в конце источника. Команда выше с завершающим слэшем копирует содержимое каталога site внутрь files. Тот же вызов без слэша положит сам каталог site внутрь files, и через месяц там вырастет files/site/site/site. Правило простое: слэш означает содержимое, его отсутствие означает каталог целиком.

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

Скрипт ночного бэкапа с датой в имени каталога и логом

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

#!/bin/bash
set -euo pipefail

DAY=$(date +%F)
SRC="/var/www/site"
DB_NAME="site_db"
STAGE="/var/backups/site"
REMOTE="Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.:/backups/site"

mkdir -p $STAGE/$DAY

mysqldump --defaults-extra-file=/root/.my.cnf --single-transaction $DB_NAME | gzip > $STAGE/$DAY/db.sql.gz

rsync -a $SRC/ $STAGE/$DAY/files/

rsync -az $STAGE/ $REMOTE/

find $STAGE -maxdepth 1 -mindepth 1 -type d -mtime +7 -exec rm -r {} +

echo "$(date) копия $DAY собрана и отправлена" >> /var/log/site-backup.log

Логика простая. Промежуточный каталог с датой в имени даёт атомарность: на удалённый сервер уезжает законченный срез за конкретный день, а не недописанная смесь. Ротация локальная чистит всё старше недели, на приёмнике дни копятся отдельно, и их ротация настраивается по тем же правилам find. Строка set -euo pipefail останавливает скрипт при первой ошибке, а не докручивает сценарий с дырой внутри. Строку с датой удобно дополнять и временем, если копий в сутки несколько: формат с часами и минутами даст внутри дня отдельные срезы, а ротация по возрасту продолжит работать как надо.

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

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

Расписание в cron и проверка того, что копии живые

Скрипт кладётся в /root/backup-site.sh, права 700, и вешается на расписание через crontab -e:

30 3 * * * /root/backup-site.sh

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

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

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

Ротация, инкрементальные копии и типичные ошибки rsync

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

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

Список классических ошибок короткий, но каждый пункт стоил кому-то выходной. Хвостовой слэш уже разобран выше, это первая причина файлов, лежащих не там, где ждали. Опечатка -delete вместо --delete превращается в попытку выполнить несуществующую команду lete в качестве шелла, и сообщение об ошибке выглядит загадочно, пока не вглядеться в лишний дефис. Права: -a переносит владельцев только под root, обычный пользователь получит файлы на своё имя. Нестандартный порт SSH задаётся через -e с указанием порта соединения. Сжатие -z внутри локальной сети жжёт процессор бесплатно, на гигабитном канале между стойками оно не нужно.

Порядок восстановления сайта из копии и метрики выживания

Копия имеет смысл вместе с отработанным путём обратно. Команды зеркальны копированию:

rsync -az Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.:/backups/site/2026-01-15/files/ /var/www/site/
gunzip < db.sql.gz | mysql --defaults-extra-file=/root/.my.cnf site_db

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

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

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