Короткий ответ

Порядок: новый Ubuntu 22.04/24.04 → nginx/Apache + PHP-FPM 8.2/8.3 → копия файлов в /var/www/site.example и БД → vhost с тем же server_name → TLS (временный --resolve / DNS-01 / уже существующие файлы) → функциональный тест без смены публичного DNS → снижение TTL заранее → cutover A/AAAA на 10.0.20.40 → наблюдение → старый сервер не уничтожать, пока TTL жив. Не начинайте с смены DNS «чтобы сразу смотреть».

Чеклист DNS: A на старый сервер. 404 после: document root. Mixed после https: mixed.

Симптомы и как отличить

  • DNS сменили, сайт 502/404, откатывать TTL час.
  • Certbot не выпускает, HTTP-01 идёт на старый IP.
  • Пользователи пишут в старую БД после «переноса файлов».
  • rm -rf старого корня в день X.

Возможные причины

  1. Нет hosts-теста.
  2. Другой путь public/ vs корень.
  3. PHP 8.3 на новом, плагин только 8.2.
  4. Сокет FPM не тот.
  5. search-replace http→https забыли.
  6. cron/письма всё ещё со старого.
  7. Заражение перенесли с собой — малварь чистить до или на копии, не разносить.

Диагностика

На новом сервере:

sudo nginx -t
systemctl is-active nginx php8.3-fpm
curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | head
sudo -u www-data wp --path=/var/www/site.example option get siteurl
df -h /var/www

Сравните checksum число файлов с источником. Прогоните login, форму, оплату на staging-копии.

Решение

0. За 48 часов

TTL A/AAAA/www → 300. Инвентарь: DNS, почта MX (не трогать), FTP, cron, SSL, объём du -sh.

1. База нового хоста

Пакеты nginx, php8.3-fpm и расширения по фактическому php -m старого. Пользователь www-data, каталог /var/www/site.example. Firewall: 80/443 с мира, SSH с jump, не ufw disable.

2. Копия данных

С старого (пример):

sudo rsync -aH --numeric-ids --delete -e ssh /var/www/site.example/ admin@10.0.20.40:/var/www/site.example/

--delete опасно, если путь неверный. Сначала без --delete, сверка. БД:

sudo -u www-data wp --path=/var/www/site.example db export - | gzip > /root/site.example.sql.gz

Перенос дампа scp, импорт на новом в новую БД, новые credentials в wp-config.php. Не светите пароль в bash history осознанно (wp config set).

sudo tar -C /var/www -czf /root/site.example-src-$(date +%F).tar.gz site.example

3. Vhost и PHP

Как в статьях nginx/Apache/FPM: root, try_files, unix-сокет /run/php/php8.3-fpm.sock. nginx -t. Права 755/644, не 777.

4. TLS до публичного DNS

Варианты: (a) скопировать /etc/letsencrypt с правами 600 на ключи, поправить пути, reload; (b) DNS-01; (c) выпуск HTTP-01 после cutover, до него — самоподписанный только для --resolve теста (браузер ругнётся, curl -k для функционала). Предпочтительно перенести LE lineage и после cutover renew. Не certbot --force-renewal на каждом шаге. HTTP-01 с новым IP до смены A не сработает у CA.

5. URL в приложении

sudo -u www-data wp --path=/var/www/site.example search-replace 'http://site.example' 'https://site.example' --all-tables --dry-run

Затем без --dry-run, если список ок. Хосты только --resolve.

6. Проверка hosts

На рабочей станции администратора:

# Windows: C:\Windows\System32\drivers\etc\hosts
# 10.0.20.40 site.example www.site.example

Либо только curl --resolve. Чеклист: главная, ЧПУ, login, медиа, POST формы, cron wp cron event run. Сравните HTML title с ожидаемым.

7. Freeze и финальный delta rsync

Короткое окно: maintenance на старом, повторный rsync и дамп дельты, импорт, снять maintenance на новом.

8. DNS cutover

A/AAAA/www → 10.0.20.40 (или CDN origin). Следите dig. Старый сервер: можно nginx return 302 только если ещё принимает Host — лучше оставить как rollback. Не выключайте 48 ч.

9. После

certbot renew --dry-run, cron, очередь писем, мониторинг на новый IP. Выкиньте hosts у админов. 404/502 — по статьям раздела, не повторный слепой перенос.

Сценарий: почта, cron и очереди после cutover

Сайт на 10.0.20.40, а wp-cron и systemd timers писем остались на старом IP — заказы двоятся или теряются. Перенесите crontab www-data, очереди Redis/БД, webhook URL платёжки на новый Host. SPF: если веб-сервер шлёт почту сам, добавьте новый ip4:10.0.20.40, старый уберите после отключения.

Сертификат: после смены A сразу certbot renew --dry-run. HTTP-01 теперь попадает сюда. Если копировали /etc/letsencrypt, проверьте timer и права privkey.pem 600.

Rollback: DNS назад на старый A только если старый стенд не уничтожен и БД не писали только в новый (иначе потеря заказов). Freeze обязан быть коротким и двусторонне согласованным.

Не делайте финальный rsync с --delete в /var/www, если в команде опечатка в пути. Сначала --dry-run. Не rm -rf старого webroot в ночь cutover: TTL ещё приводит людей, плюс откат.

Проверка с LTE (не офисный DNS): главная, ЧПУ, корзина, админка. Сверьте date в футере/заказе, что запись ушла в новую БД.

Как проверить, что проблема устранена

dig +short A site.example
curl -sI https://site.example/ | head
sudo certbot renew --dry-run --cert-name site.example

Публичный A = новый IP. Контент свежий (заказ/пост после freeze на новом). Старый access.log затихает. HTTPS валиден без -k. Сотрудники без hosts видят новое.

Rollback: вернуть A на старый IP, если новый не готов и старый не снесён.

Если не помогло

  • Часть мира на старом — TTL, DNS.
  • Почта сломалась — вы тронули MX/SPF.
  • Сессии пользователей сбросились — соли/Redis на новом пустой, это ожидаемо.
  • Плагины лицензий привязаны к IP — обновите у вендора.

Профилактика

  • Письменный runbook с TTL и freeze.
  • Staging = тот же major PHP.
  • Бэкап обоих концов до cutover.
  • Не хранить единственную копию только на старом диске.

FAQ

Можно ли CNAME на новый hostname вместо A?

Apex часто нельзя CNAME. www — да, если так принято. Проще A на IP origin или CDN.

rsync во время работы сайта

Для финала нужен freeze. Итеративные rsync до окна — да.

Docker на новом, systemd на старом

Проверьте порты и NPM, не копируйте пути сокетов слепо.

Нужен ли одинаковый путь /var/www/site.example?

Сильно упрощает. Другой путь — правьте root и абсолютные крон-пути.

Когда сносить старый сервер?

Когда access.log пуст несколько TTL, бэкап нового проверен restore-тестом, LE renew идёт сюда. Не в день cutover и не через rm -rf webroot «для места».