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

Если после переноса site.example «то новый, то старый», смотрите DNS, не PHP. Записи A/AAAA должны указывать на новый origin 10.0.20.40 (или на CDN, который уже смотрит на него). Снизьте TTL заранее, смените записи, ждите максимальный старый TTL. Проверку контента делайте curl --resolve / файл hosts, не надеясь, что «у меня уже открылось». Пока DNS старый, Let's Encrypt HTTP-01 тоже ходит на старый IP.

План cutover: перенос. 404 на новом при правильном DNS — document root.

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

  • Соседи видят новый сайт, вы — старый (или наоборот).
  • dig A site.example10.0.20.40.
  • HTTPS ошибка имени: попали на чужой default vhost старого сервера.
  • NPM/панель открывается, публичный сайт нет.
ИнструментЧто проверяет
dig у авторитетного NSчто выписали
dig @8.8.8.8публичный резолвер, не ваша политика если запрещён — используйте свой рекурсор
curl --resolveorigin напрямую
браузеркэш + DoH

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

  1. A не сменили, сменили только www или наоборот.
  2. AAAA всё ещё на старый IPv6, клиенты предпочитают v6.
  3. TTL 86400, сменили запись час назад.
  4. Cloudflare/прокси: серые/оранжевые облака, origin IP в панели не тот.
  5. Несколько A round-robin, один старый.
  6. Локальный hosts у админа на старый IP.
  7. Кэш рекурсора офиса.

Диагностика

1. Что видит мир и что вы хотите

dig +noall +answer A site.example www.site.example
dig +noall +answer AAAA site.example
dig +noall +answer NS site.example
dig +ttlunits +noall +answer A site.example

Сверьте с панелью регистратора. IP нового стенда: 10.0.20.40.

2. Авторитетные NS

dig +short NS site.example
dig A site.example @ns1.example-registrar   # подставьте реальный NS из предыдущей команды

Не выдумывайте имя NS. Берите из ответа.

3. Origin напрямую

curl -sI --resolve site.example:443:10.0.20.40 https://site.example/ | head
curl -sI --resolve site.example:443:<OLD_IP> https://site.example/ | head

Подставьте фактический старый IP из dig, не из памяти.

4. Hosts на рабочей станции

# Linux/macOS
grep site.example /etc/hosts
# Windows PowerShell
Get-Content $env:SystemRoot\System32\drivers\etc\hosts | Select-String 'site.example'

5. LE

Если renew падает connection refused на чужом IP — это этот кейс + certbot.

Решение

Сценарий A. Плановая смена

  1. За 24–48 ч до cutover поставьте TTL 300 на A/AAAA/www.
  2. Дождитесь истечения старого TTL.
  3. Поднимите сайт на 10.0.20.40, проверьте --resolve.
  4. Смените A/AAAA (и www) на новый адрес.
  5. Наблюдайте dig с нескольких рекурсоров.
  6. Старый сервер не гасите, пока TTL не выдохся (редирект/статика «мы переехали» опционально).

Не уменьшайте TTL в момент смены — поздно для тех, кто уже закэшировал час назад.

Сценарий B. Забыли AAAA

Удалите устаревший AAAA или поставьте корректный IPv6 нового сервера. Битый AAAA = часть клиентов на старом/мёртвом узле при живом A.

Сценарий C. Несколько A

Оставьте один канонический origin (или два осознанных в балансировке). Лишний старый A — лотерея.

Сценарий D. CDN

Смените origin IP в панели CDN. Публичный A может остаться anycast CDN — это нормально, тогда «старый сервер» в A вы не увидите. Проверяйте настройки origin, не только dig site.example.

Сценарий E. Локальный hosts после теста

Удалите тестовые строки после cutover, иначе будете чинить «не тот сервер».

Сценарий F. Раздвоенная зона и старый NS

dig NS site.example должен совпадать с NS у регистратора. Если сменили DNS-хостинг, но регистратор всё ещё делегирует старые NS, вы правите «красивую» панель, а мир читает старую зону со старым A. Сверьте glue и whois/RDAP NS. Не путайте записи в Cloudflare с записями на registrar DNS — активен один набор NS.

После cutover обновите SPF/A для сайта, если SPF содержал старый IP как ip4: для веб-форм. MX не меняйте «за компанию». CAA, если есть, должен разрешать Let's Encrypt на новом origin.

Проверка с нескольких резолверов:

for s in 1.1.1.1 8.8.8.8 9.9.9.9; do echo $s; dig +short A site.example @$s; done

Если политика запрещает публичные резолверы — используйте внутренние рекурсоры филиалов. Расхождение = кэш TTL или split-horizon.

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

dig +short A site.example
dig +short A www.site.example
dig +short AAAA site.example
curl -sI https://site.example/ | head

A = 10.0.20.40 (если без CDN). HTTPS — новый контент (метку в футере/заголовок X-Served-By если добавите временно). С телефона через LTE — не офисный кэш. Certbot dry-run с нового IP.

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

  • SERVFAIL: сломали DNSSEC, DS на старой ZSK. Это уже зона DNSSEC, не nginx.
  • NXDOMAIN: удалили apex, остался только www.
  • GeoDNS отдаёт разные IP — сверьте view.
  • Почта: MX не обязан совпадать с A сайта; не трогайте MX «заодно».

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

  • Чеклист миграции с TTL.
  • Инвентаризация A, AAAA, www, dev, mail.
  • Мониторинг dig с внешнего зонда.
  • Не держать TTL 86400, если миграции частые; 3600 разумный компромисс.
  • Перед cutover запишите dig A/AAAA/NS в заявку: иначе не докажете, что «у клиента старый IP» — это кэш, а не ваша зона.

FAQ

Сколько ждать TTL 3600?

До часа после смены плюс кэш промежуточных. Для страховки — 2×TTL.

CNAME www на apex

Apex обычно A/AAAA (или ALIAS у регистратора). Не ставьте CNAME на apex, если DNS провайдер это запрещает.

Можно ли /etc/hosts у всех сотрудников вместо смены DNS?

Только для теста админов. Пользователей так не переключить.

Старый сервер как 301 на новый IP?

HTTP-редирект по IP чужой Host сломает TLS. Лучше DNS. Временно — страница «обновите кэш» на HTTP.

Glue записи?

Только если NS на том же домене. Для сайта на 10.0.20.40 обычно не ваш случай.