Переезд между хостингами почти никогда не бывает мгновенным: часть посетителей ещё несколько часов приходит на старый IP, пока обновляются кэши DNS. Эта статья — про то, как свести окно недоступности записи к нескольким минутам и не потерять за это время заказы, сессии и письма. Все команды даны под связку Ubuntu 24.04 LTS, nginx 1.24, PHP 8.3 и MySQL 8.0; отличия MariaDB 10.11 отмечены отдельно.
Что значит «без простоя»
Сайт всё время остаётся доступным на чтение: страницы открываются, каталог виден, поиск работает. Отключается на несколько минут только запись — формы, оформление заказа, загрузка файлов, регистрация. Это единственный честный способ гарантировать, что заказ, оформленный в момент переключения, не потеряется вместе со старой базой.
Главная опасность здесь не техническая, а логическая: расщепление состояния. Если оба сервера какое-то время принимают записи, вы получаете два расходящихся набора данных, и сводить их придётся руками. Поэтому запись выключается ровно один раз — на старом сервере — и включается тоже ровно один раз, уже на новом.
Что переезжает, кроме файлов и базы
Больше всего неприятных сюрпризов приходится не на файлы и не на дамп, а на то, что переносится вручную и потому забывается. Составьте список до того, как начнёте копировать.
| Что переносим | Чем | Что теряется молча |
|---|---|---|
| Файлы сайта | rsync -aHAX --delete, на приёмнике под root | владельцы файлов, если на приёмнике нет прав на chown |
| База MySQL | mysqldump --routines --triggers --events | процедуры, триггеры, события, кодировка и collation |
| Сессии, кэш, очереди | копия dump.rdb из Redis | все пользователи разлогинены, задания очереди исчезли |
| Сертификаты Let's Encrypt | rsync /etc/letsencrypt/ от root | симлинки live/ на archive/ и таймер автопродления |
| Конфиги nginx | rsync /etc/nginx/ от root | путь к сокету PHP-FPM от старой версии PHP — это 502 |
| Cron и systemd-таймеры | только вручную | задания из /etc/cron.d и crontab других пользователей |
| Ключи DKIM | копия каталога ключей вашего MTA | подпись писем; TXT-запись селектора при этом менять не нужно |
| SPF | правка DNS заранее | исходящая почта с нового IP уходит в спам |
Сессии, кэш и очереди
На типовом сайте сессии и кэш чаще лежат в Redis или Memcached, а не в файлах и не в MySQL. Ни rsync каталога сайта, ни mysqldump их не переносят, а сайт после переключения выглядит рабочим — просто все авторизованные пользователи разлогинены, корзины пусты, а необработанные задания из очереди исчезли вместе со старым сервером. Сначала посмотрите, что вообще используется:
grep -E 'SESSION_DRIVER|CACHE_STORE|QUEUE_CONNECTION' .envЕсли это Redis, его переносят отдельно — после остановки воркеров, чтобы в снимок не попала половина обработанного задания:
# на старом сервере: сохраняем снимок и убеждаемся, что он свежий
redis-cli BGSAVE
sleep 5
redis-cli LASTSAVE
sudo rsync -a /var/lib/redis/dump.rdb root@203.0.113.10:/var/lib/redis/dump.rdb
# на новом сервере: вернуть владельца и перезапустить, иначе снимок не загрузится
sudo chown redis:redis /var/lib/redis/dump.rdb
sudo systemctl restart redis-serverCron и systemd-таймеры
Периодические задания на Ubuntu живут как минимум в пяти местах, и rsync каталога сайта не копирует ни одно из них. Проверять надо все:
# пользовательские crontab всех пользователей, а не только текущего и www-data
sudo ls -1 /var/spool/cron/crontabs/
for u in $(sudo ls -1 /var/spool/cron/crontabs/); do
echo "=== $u ==="
sudo crontab -u "$u" -l
done
# системные задания — их легко забыть; сюда же пакеты кладут свои, включая certbot
sudo cat /etc/crontab
sudo ls -l /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/
systemctl list-timers --allСобираем новый сервер как зеркало старого
Сертификаты и конфиги nginx
Каталог /etc/letsencrypt копируется отдельной командой и обязательно от root. Причин три. Подкаталоги archive и keys имеют режим 0700 root:root — непривилегированный пользователь получит «Permission denied» по каждому файлу и код возврата 23, причём live/ при этом создастся и ошибку легко пролистать. Файлы в live/example.com/ — это относительные символические ссылки на ../../archive/example.com/: копировать только live/ или с ключом -L нельзя, иначе получите битые ссылки либо файлы без истории, и certbot renew потом сломается.
# от root на старом сервере; -a сохраняет симлинки live/ на archive/, -L использовать НЕЛЬЗЯ
sudo rsync -aHAX --numeric-ids /etc/letsencrypt/ root@203.0.113.10:/etc/letsencrypt/
sudo rsync -aHAX --numeric-ids /etc/nginx/ root@203.0.113.10:/etc/nginx/Пакет certbot на новом сервере нужно установить отдельно: копирование /etc/letsencrypt не создаёт certbot.timer, и без него автопродление не заработает. Заодно откройте /etc/letsencrypt/renewal/example.com.conf — там записан способ проверки со старого сервера (authenticator = nginx или webroot_path), и на новом он может не соответствовать реальности.
Сокет PHP-FPM
Это самая частая причина 502 сразу после переезда. Старый сервер почти наверняка был на PHP 8.1 или 8.2, и в скопированном конфиге стоит fastcgi_pass unix:/run/php/php8.1-fpm.sock;. На Ubuntu 24.04 с PHP 8.3 такого сокета нет: сокет называется /run/php/php8.3-fpm.sock, юнит — php8.3-fpm.service, пул лежит в /etc/php/8.3/fpm/pool.d/www.conf, логи — в /var/log/php8.3-fpm.log.
Типичная ошибка: убедиться, что
nginx -tпроходит, и считать конфиг рабочим. Путь к сокету на этапе теста конфигурации не проверяется —nginx -tскажет «syntax is ok», а сайт будет отдавать 502 на каждый PHP-запрос.
grep -rn 'fastcgi_pass' /etc/nginx/
ls -l /run/php/
systemctl status php8.3-fpm --no-pager
sudo nginx -t && sudo systemctl reload nginxПривычку nginx -t && systemctl reload nginx стоит закрепить сразу: дальше в статье будет несколько правок конфига, и каждая вступает в силу только после перезагрузки nginx.
Расширения и настройки PHP
Сравнивать наборы расширений нужно у того SAPI, под которым работает сайт. В PATH лежит CLI, а сайт крутится под FPM, и наборы у них различаются штатно: в /etc/php/8.3/cli/conf.d и /etc/php/8.3/fpm/conf.d разные симлинки, а opcache в CLI по умолчанию выключен. Сравнив CLI, вы получите чистый diff и уверенность, что всё в порядке, — а сайт упадёт на отсутствующем в FPM imagick или intl.
# сравниваем именно FPM SAPI, а не CLI: наборы расширений у них разные
ssh deploy@198.51.100.20 'php-fpm8.2 -m' | sort > /tmp/old-modules.txt # версия старого сервера
php-fpm8.3 -m | sort > /tmp/new-modules.txt
diff /tmp/old-modules.txt /tmp/new-modules.txtВторая по частоте причина «на старом же работало» — разошедшиеся значения ini. Проверьте их на обоих серверах, а потом отдельно посмотрите, что переопределяет пул FPM:
for h in 198.51.100.20 203.0.113.10; do
echo "=== $h ==="
ssh deploy@$h 'php -i | grep -E "^(memory_limit|upload_max_filesize|post_max_size|max_execution_time|date.timezone) "'
done
grep -E 'php_admin_value|php_value' /etc/php/8.3/fpm/pool.d/www.confЗа неделю: почта, SPF и ключи DKIM
Проверять почту после переезда поздно: с момента смены записи A и до правки SPF вся исходящая почта улетает в спам или отбивается с 550 SPF check failed. У SPF-записи есть собственный TTL, который тоже надо снижать заранее.
Добавьте новый IP в SPF, не удаляя старый, и оставьте оба на время переходного периода: v=spf1 ip4:198.51.100.20 ip4:203.0.113.10 ~all. Убрать старый IP можно через неделю после переезда. DKIM от IP не зависит вообще: это пара «приватный ключ в конфиге MTA плюс публичный ключ в TXT-записи селектора». Перенесите приватные ключи со старого сервера (/etc/opendkim/keys/ или каталог ключей вашего MTA) — TXT-запись при этом менять не нужно. После переключения проверьте обе записи:
dig example.com TXT +short | grep spf1
dig mail._domainkey.example.com TXT +shortЗа сутки: TTL и полный список записей
TTL — время, которое резолверы держат ответ в кэше. Пока не истекло прежнее значение, о новом никто не узнает, поэтому снижать TTL нужно минимум за старый TTL до переезда, а на практике — за сутки. Рабочее значение на время переезда — 300 секунд, после переезда его возвращают обратно.
Типичная ошибка: снизить TTL за час до переключения. Если раньше стояло 86400, часть резолверов узнает о новом значении только через сутки, а до тех пор будет держать старый IP ровно столько, сколько записано в уже отданном ответе.
Смотреть надо все записи, которые придётся переключать, а не только A. Если у зоны есть AAAA — а на современных хостингах IPv6 включён по умолчанию, — клиенты с IPv6 продолжат ходить на старый сервер сколько угодно долго. То же касается www: это может быть CNAME, и правка A у апекса на него не подействует.
dig example.com A +noall +answer
dig example.com AAAA +noall +answer
dig www.example.com ANY +noall +answer # A / AAAA / CNAME
dig example.com TXT +short | grep spf1Запрос ANY надёжнее задавать авторитативному серверу зоны: часть публичных резолверов отвечает на него по RFC 8482 и полного списка записей не покажет.
Проверять, разошёлся ли новый TTL, нужно с явно указанным резолвером. Без @-адреса dig идёт в /etc/resolv.conf, где на Ubuntu 24.04 стоит stub systemd-resolved 127.0.0.53, а за ним — несколько апстримов провайдера с независимыми кэшами: два запроса подряд могут уйти к разным апстримам, и убывающего счётчика вы просто не увидите.
# резолвер фиксируем явно, иначе два запроса могут уйти к разным апстримам
for r in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo -n "$r: "; dig @$r example.com A +noall +answer
done
sleep 30
for r in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo -n "$r: "; dig @$r example.com A +noall +answer
doneДержите в голове, что TTL 300 — это ориентир для большинства, а не гарантия для всех. Часть публичных и провайдерских резолверов принудительно поднимает мелкие TTL до своего минимума (нередко 30–60 секунд, у некоторых корпоративных — до часа), а браузеры держат собственный кэш DNS поверх системного. Планируйте, что единичные клиенты придут на старый IP ещё несколько часов; именно поэтому старый сервер в конце превращается в прокси, а не гасится.
Первая синхронизация файлов
Файлы копируют заранее и целиком, чтобы в момент переключения остался только короткий догоняющий проход. Набор исключений задайте один раз переменной и используйте во всех проходах: в rsync --exclude не только пропускает файлы при передаче, но и защищает их на приёмнике от --delete. Если убрать исключения в поздних проходах, вы зальёте на новый сервер многогигабайтный кэш и логи, а --delete снесёт то, что новый сервер успел нагенерировать за время тестов.
# один набор исключений на все проходы
RSYNC_OPTS=(-aHAX --numeric-ids --delete
--exclude 'storage/framework/cache/'
--exclude 'var/cache/'
--exclude '*.log')
# слэш в конце источника обязателен: копируем содержимое каталога, а не сам каталог
time rsync "${RSYNC_OPTS[@]}" --info=progress2 \
--rsync-path='sudo rsync' \
/var/www/example.com/ deploy@203.0.113.10:/var/www/example.com/Ключ --numeric-ids сохраняет числовые UID и GID, чтобы права не разъехались при другом порядке создания пользователей. Но сохранение владельца требует прав на chown на приёмнике: непривилегированный deploy просто не сможет назначить чужой UID, rsync это молча проигнорирует, и все файлы окажутся deploy:deploy — www-data не сможет писать в storage/, var/cache/ и public/uploads/. Отсюда --rsync-path='sudo rsync'; для этого на новом сервере нужна строка в sudoers:
deploy ALL=(root) NOPASSWD: /usr/bin/rsyncЕсли давать такие права не хочется, честнее не переносить владельцев вовсе: добавьте --no-owner --no-group, уберите --numeric-ids и один раз выставьте владельца на новом сервере.
sudo chown -R www-data:www-data /var/www/example.comПроверка нового сервера до переключения
Чтобы точно знать, на какой сервер вы попали, повесьте на новом сервере отладочный заголовок. Важно, где именно: add_header наследуется в location только при условии, что в этом location нет ни одного своего add_header. В любом реальном конфиге PHP-сайта свои заголовки есть, и серверный X-Origin-Host там будет отброшен — вы не найдёте его в ответе и решите, что попали на старый сервер, хотя попали на новый.
location ~ \.php$ {
add_header X-Origin-Host "new" always; # обязателен здесь: наследование от server отменяется
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}Дальше открываем новый сервер под настоящим доменом, не трогая DNS. Для командной строки удобнее --resolve: он не требует правки системного файла и не оставляет следов.
curl -sI --resolve example.com:443:203.0.113.10 https://example.com/ | grep -i x-origin-hostДля проверки в браузере правится /etc/hosts, и вписывать туда нужно оба адреса: если у домена есть AAAA, getaddrinfo предпочтёт IPv6 из DNS одинокой строке с IPv4 из hosts, и вы будете тестировать старый сервер.
203.0.113.10 example.com www.example.com
2001:db8::10 example.com www.example.com# macOS: сбросить локальный кэш DNS после правки hosts
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponderТак проверяется всё, что инициирует ваш браузер: вход в админку, загрузка файлов, отрисовка страниц, работа с базой. Чего через hosts проверить нельзя — это всё, что приходит на сайт снаружи, потому что оно идёт по данным DNS, то есть на старый сервер:
вебхуки платёжного провайдера — сквозной сценарий оплаты так не проверяется в принципе, проверяется только редирект на платёжную форму;
OAuth-колбэки и любые ответы внешних сервисов;
входящая почта — она идёт по MX, и
/etc/hostsна неё не влияет вообще;исходящая почта уйдёт с нового IP, которого может ещё не быть в SPF, — тест даст ложноотрицательный результат.
Эти сценарии проверяют либо на тестовом домене, указывающем на новый IP, либо в песочнице провайдера, либо сразу после переключения.
Режим только чтения
Флаг режима только чтения должен лежать вне каталога, который синхронизируется rsync с ключом --delete. Иначе догоняющий проход принесёт флаг со старого сервера на новый, и новый сервер стартует в режиме только чтения. Хуже обратный случай: если после переключения кто-то ещё раз прогонит rsync со старого сервера, флаг вернётся на боевой сервер и запись отключится без единой строчки в логе.
<?php
// public/index.php, до подключения фреймворка
// путь вне /var/www — иначе флаг приедет на новый сервер вместе с rsync
$readOnly = is_file('/etc/example.com/readonly.flag');
if ($readOnly && ! in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD', 'OPTIONS'], true)) {
http_response_code(503);
header('Retry-After: 600');
exit('Сайт временно доступен только для чтения. Попробуйте через 10 минут.');
}Если менять путь не хочется, минимум — добавить --exclude 'storage/readonly.flag' во все проходы rsync и написать об этом в инструкции для дежурного.
Тот же режим можно включить и на уровне nginx. Директива if допустима только в контексте server и location: в контексте http nginx откажется стартовать с ошибкой "if" directive is not allowed here. И обязательно оставьте проход для вебхуков — многие платёжные провайдеры при 503 делают считанное число ретраев, а некоторые не делают ни одного, так что подтверждения оплат за всё окно вы просто потеряете.
server {
# ...
add_header Retry-After 600 always;
# OPTIONS не отсекаем, иначе сломается CORS-preflight на фронтенде
if ($request_method !~ ^(GET|HEAD|OPTIONS)$) {
return 503;
}
# вебхуки должны продолжать приниматься — location приоритетнее if в server-контексте
location = /webhooks/payment {
try_files $uri /index.php$is_args$args;
}
}Перенос базы данных
Сначала снимите с боевой базы её кодировку и сортировку — они понадобятся при создании базы на новом сервере:
SELECT @@character_set_database, @@collation_database;Дампить лучше отдельным пользователем. На MySQL 8.0.21 и новее mysqldump --single-transaction берёт LOCK INSTANCE FOR BACKUP, и для этого нужна привилегия BACKUP_ADMIN: пользователь с одним SELECT получит Access denied ровно в момент переключения — худшее место для такого сюрприза.
GRANT SELECT, SHOW VIEW, TRIGGER, EVENT, PROCESS, RELOAD ON *.* TO 'backup'@'localhost';
GRANT BACKUP_ADMIN ON *.* TO 'backup'@'localhost'; -- MySQL 8.0, для MariaDB не нужноКлюч --single-transaction снимает согласованный срез InnoDB внутри одной транзакции, не блокируя INSERT и UPDATE. Но «не блокирует запись» верно только для DML: дамп удерживает метаданные-локи на каждой таблице, поэтому любой ALTER TABLE, запущенный во время дампа, встанет в очередь, а за ним встанут все последующие запросы к этой таблице. Проще говоря, деплой миграции параллельно с дампом кладёт сайт наглухо.
mysqldump --single-transaction --quick --routines --triggers --events \
--set-gtid-purged=OFF --default-character-set=utf8mb4 \
-u backup -p shop_db > /root/shop_db.sqlРазберём ключи, которые чаще всего забывают. Без --routines --triggers --events хранимые процедуры и события никуда не уедут, и вы этого не заметите, пока не должно будет сработать первое событие. Без --default-character-set=utf8mb4 на старых сборках дефолтом окажется latin1 и в дампе будут «кракозябры». А --set-gtid-purged=OFF нужен потому, что на MySQL 8 с включёнными GTID дамп по умолчанию содержит SET @@GLOBAL.GTID_PURGED, и импорт падает с @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty.
При импорте помните: mysqldump db_name без ключа --databases не выводит ни CREATE DATABASE, ни USE. На чистом сервере импорт упадёт с ERROR 1049 (42000): Unknown database 'shop_db'. Создайте базу сами и обязательно с той же сортировкой, что была на старом сервере: дефолт MySQL 8.0 на Ubuntu 24.04 — utf8mb4_0900_ai_ci, а на старом сервере почти наверняка было utf8mb4_unicode_ci, и расхождение вылезет как Illegal mix of collations на первом же JOIN со сравнением строк.
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS shop_db \
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql --default-character-set=utf8mb4 -u root -p shop_db < /root/shop_db.sqlПромежуточный файл можно не создавать, но тогда следите за кодом возврата: по умолчанию код пайплайна — это код последней команды, то есть ssh, и ошибка mysqldump в середине потока даст обрезанный, но «успешно» импортированный дамп. Учётные данные на принимающей стороне тоже нужны явно, иначе mysql запустится от deploy и в лучшем случае получит Access denied, а в худшем — под сокет-аутентификацией попадёт в чужую базу.
set -o pipefail
mysqldump --single-transaction --quick --routines --triggers --events \
--set-gtid-purged=OFF --default-character-set=utf8mb4 \
-u backup -p shop_db \
| ssh deploy@203.0.113.10 \
'mysql --defaults-file=/home/deploy/.my.cnf --default-character-set=utf8mb4 shop_db'
echo "exit=$?" # без pipefail здесь всегда был бы 0Файл /home/deploy/.my.cnf должен содержать секцию [client] с логином и паролем и иметь права 600.
Чек-лист переключения
Объявить окно и включить режим только чтения на старом сервере.
Остановить воркеры очередей и cron на старом сервере, дождаться завершения текущих заданий.
Снять финальный дамп базы, перенести и импортировать его на новом сервере; там же обновить снимок Redis.
Прогнать догоняющий rsync — сухой прогон, затем боевой.
Проверить новый сервер через
--resolve: заголовокX-Origin-Host, вход в админку, чтение страниц.Изменить записи A и AAAA — для апекса и для
www, если это не CNAME. Если AAAA на новом сервере нет, её удаляют заранее, за старый TTL до переключения.Снять флаг только чтения на новом сервере, включить там cron и воркеры.
Переключить старый сервер в режим прозрачного прокси на новый IP.
Догоняющий проход выполняется с тем же набором исключений, что и первый. Сухой прогон нужен, чтобы увидеть, что именно изменилось, — при потерянных исключениях он покажет сотни строк кэша и логов, и head спрячет реальные изменения.
# сухой прогон: ничего не меняем, только смотрим
rsync "${RSYNC_OPTS[@]}" --dry-run --itemize-changes --rsync-path='sudo rsync' \
/var/www/example.com/ deploy@203.0.113.10:/var/www/example.com/ | head -50
# сам догоняющий проход
time rsync "${RSYNC_OPTS[@]}" --rsync-path='sudo rsync' \
/var/www/example.com/ deploy@203.0.113.10:/var/www/example.com/Старый сервер как прокси
Оставлять старый сервер «в режиме только чтения на пару недель» — плохая идея. Всё это время посетители со старым IP в кэше видят базу, замороженную на момент дампа: старые цены, отсутствующие новые товары, чужие сессии и «пропавшие» заказы, которые они только что оформили. Поисковый робот со старого IP соберёт 503 на все не-GET запросы и устаревший контент.
Единственная корректная роль старого сервера после переключения — прозрачный прокси на новый IP. Тогда поздний трафик обслуживается правильными данными, а запись можно включить сразу после импорта.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass https://203.0.113.10;
proxy_ssl_server_name on; # иначе SNI не уйдёт и новый сервер отдаст дефолтный vhost
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Без proxy_ssl_server_name on; прокси молча не заработает так, как вы ожидаете: nginx не отправит SNI, новый сервер ответит сертификатом дефолтного сервера, и вы получите 502. Форма listen 443 ssl http2; здесь правильная: отдельная директива http2 появилась только в nginx 1.25.1, а в Ubuntu 24.04 приезжает nginx 1.24.
Сколько на самом деле длится окно
Считать надо не «сколько идёт дамп», а сумму всех шагов — они не перекрываются. TTL начинает тикать только после смены записи, то есть после дампа и rsync, а не параллельно с ними.
Пример расчёта для базы на 2 ГБ: финальный дамп и импорт — около 5 минут, догоняющий rsync — около 40 секунд, TTL — 300 секунд. Для посетителя, чей резолвер уже подхватил новый IP, запись недоступна примерно 6 минут; для того, у кого в кэше ещё старый ответ, — до 11 минут. Прокси на старом сервере сокращает второе число до первого. Это именно пример расчёта, а не результат замера: свои цифры вы получите на пробном прогоне, и импорт с построением индексов InnoDB обычно занимает больше, чем сам дамп.
Пока флаг только чтения не снят, откат бесплатный: вы просто возвращаете записи DNS на старый IP. После того как новый сервер начал принимать записи, откат означает потерю всего, что успели записать, — поэтому решение «переключаемся или откатываемся» принимается до шага 7 чек-листа.
После переключения
Проверьте по порядку: работает ли запись на новом сервере, включились ли cron и воркеры, уходит ли почта, отвечает ли автопродление сертификатов. Отдельно убедитесь, что вебхуки платёжного провайдера доходят: они первыми покажут, что снаружи домен уже указывает куда надо.
Дальше следим за трафиком на старом сервере. Наблюдение идёт неделю-две, а logrotate крутит логи nginx ежедневно, и здесь есть подвох: tail -f держит открытым файловый дескриптор, а не имя. После ротации он продолжит следить за переименованным access.log.1, куда nginx больше не пишет, вывод замрёт, и вы сделаете вывод «трафика нет», имея живых посетителей.
# -F, а не -f: переоткрывает файл по имени после ротации logrotate
sudo tail -F /var/log/nginx/access.log
# надёжнее — посчитать запросы за сутки по всем ротациям, включая сжатые
sudo zcat -f /var/log/nginx/access.log* | awk '{print $4}' | cut -d: -f1 | sort | uniq -cКогда счётчик по суткам стабильно держится около нуля, старый сервер можно гасить. К этому моменту стоит вернуть прежний TTL, убрать старый IP из SPF и убедиться, что резервные копии снимаются уже с нового сервера.







