Перенос VPS с одного хостера на другой без даунтайма
Перенос боевого сервиса с одного хостера на другой — задача, которую делают редко, но каждый раз с замиранием сердца. У меня в этом году было два таких переезда: с зарубежного хостера на Selectel и между двумя российскими провайдерами. Расскажу схему, по которой получалось без даунтайма (или с минимальным окном в пару секунд).
Контекст: Linux-сервер с Node-приложением, Postgres, nginx, статикой. На целевой стороне — пустой VPS той же или большей конфигурации.
План на бумаге
Любой переезд я начинаю с плана, который проговариваю вслух. Минимум:
- Что переносим: какие сервисы, какие данные, какие cron, какие сертификаты.
- Куда: новый IP, домен, DNS-провайдер.
- Как: какие шаги в каком порядке.
- Что меняем DNS-ом: TTL надо снизить заранее.
- Откат: если что-то пошло не так, как возвращаемся на старый сервер.
На листочке это занимает минут 20, и за эти 20 минут обычно вылезает 2–3 пункта, о которых забыл.
Шаг 1: TTL DNS в минимум
За 24–48 часов до миграции снижаю TTL у A-записей на 60–120 секунд. Это значит, что когда я в момент миграции переключу IP, клиенты увидят новый адрес быстро.
Если этого не сделать, TTL обычно 3600 секунд (час) или 86400 (сутки). Разводка трафика в этом случае растягивается, и часть пользователей будет ходить на старый IP до конца окна.
В Cloudflare, Yandex DNS, Selectel DNS этот шаг занимает минуту: открываешь зону, у каждой записи правишь TTL.
Шаг 2: подготовка целевого сервера
На новом хостере поднимаешь VPS, устанавливаешь всё, что нужно: Node, Postgres, nginx, certbot, fail2ban, systemd-юниты твоих сервисов. Не запускаешь приложение с реальным DNS — но всё должно быть готово.
Я для этого держу Ansible-плейбук или просто bash-скрипт, который по чистой Ubuntu разворачивает мою стандартную обвязку. На новом сервере прохожу его, и через 30 минут есть «зеркало» старого окружения.
Шаг 3: первый базовый dump БД
Делаешь полный dump Postgres со старого сервера, копируешь на новый, восстанавливаешь.
ssh old-server 'pg_dumpall -U postgres' | psql -U postgres -h new-serverНа больших базах не делай через ssh-pipe — ловишь обрывы. Лучше:
ssh old-server 'pg_dumpall -U postgres > /tmp/dump.sql'
rsync -a old-server:/tmp/dump.sql ./dump.sql
rsync -a ./dump.sql new-server:/tmp/dump.sql
ssh new-server 'psql -U postgres -f /tmp/dump.sql'На новом сервере БД готова с актуальными данными на момент dump-а.
Шаг 4: репликация изменений
Между моментом dump-а и моментом переключения IP старая БД продолжает принимать записи. Если тупо перенести dump и через час переключить — данные за этот час потеряются.
Варианта два:
Логическая репликация (рекомендую)
На старом сервере поднимаешь логическую публикацию, на новом — подписку. С момента подключения новый сервер получает все изменения старого почти в реальном времени.
-- на старом
ALTER SYSTEM SET wal_level = 'logical';
SELECT pg_reload_conf();
CREATE PUBLICATION migrate_pub FOR ALL TABLES;
-- на новом
CREATE SUBSCRIPTION migrate_sub
CONNECTION 'host=<old_ip> port=5432 user=replicator password=...'
PUBLICATION migrate_pub;Когда статус подписки streaming и нет lag-а — данные синхронизированы. В момент переключения нужно будет только остановить запись на старом, дождаться, пока репликация догонит, и переключить.
Только last-mile перенос
Если логическая репликация невозможна (старый сервер на устаревшей версии Postgres, нет доступа), используется план «остановить запись + быстрый dump в окне». Минус — окно с downtime.
Шаг 5: данные приложения
Файлы пользователей, логи, конфиги — всё, что не БД. Использую rsync с инкрементальным режимом:
# первый полный sync
rsync -avz --progress old-server:/opt/myapp/uploads/ /opt/myapp/uploads/
# периодически догонять
rsync -avz --progress old-server:/opt/myapp/uploads/ /opt/myapp/uploads/В момент миграции делаешь финальный rsync, и файлы ровно те же.
Шаг 6: поднимаем приложение на новом сервере на тестовом домене
На новом сервере прописываешь nginx с временным доменом (например, new.example.ru), указываешь его на новый IP. Прогоняешь все основные сценарии: логин, регистрация, оплата, отправка письма. Проверяешь, что приложение видит БД, S3, кэш, всё настроено.
Это критический шаг. Не переключай DNS, пока на новом сервере не работает приложение в полном объёме.
Шаг 7: подготовка nginx обоих серверов
Сейчас оба сервера слушают одинаковый домен. Старый — реально, новый — пока на временном домене. Когда переключим DNS, новый начнёт принимать продакшен-трафик.
Сертификаты:
- На старом сервере остаётся валидный сертификат — он будет жить до момента переключения.
- На новом нужно либо заранее выпустить сертификат через DNS-01 challenge (тогда HTTP-validation не нужен), либо после переключения DNS быстро прогнать
certbot.
Я предпочитаю DNS-01: cert уже валиден на новом сервере, при переключении пользователи увидят правильный сертификат сразу.
Шаг 8: момент истины
Когда всё готово, делаешь миграцию. Порядок строгий:
- На старом сервере переводим приложение в read-only (или останавливаем). Запись прекращается.
- Ждём, пока логическая репликация Postgres догонит (несколько секунд обычно).
- Финальный rsync файлов.
- На новом сервере делаем
ALTER SUBSCRIPTION migrate_sub DISABLE, чтобы он стал самостоятельным. - Стартуем приложение на новом сервере под продовым доменом.
- Меняем A-запись DNS со старого IP на новый.
За счёт низкого TTL пользователи начнут уходить на новый сервер за минуту-две. Все, кто резолвил DNS до переключения, ещё какое-то время будут ходить на старый — он остался в read-only и обслуживает чтение.
Шаг 9: «двойной режим» 24 часа
Старый сервер не выключаю сразу. Держу его 24–48 часов в read-only режиме (или с проксированием на новый), смотрю на:
- Сколько запросов ещё прилетает на старый.
- Нет ли запросов, которые требуют записи.
- Нет ли расхождений в данных.
Когда трафик на старом ушёл в ноль — выключаю. На этом шаге, если что-то с новым сервером пошло не так, можно вернуться: переключить DNS обратно, реактивировать запись, разобраться.
Шаг 10: пост-миграционная проверка
- Все cron-задачи и BullMQ-воркеры работают на новом сервере.
- Бэкапы БД крутятся (новый бэкап после переезда — обязательно).
- Логи и мониторинг показывают данные. Не было упущенного шага «настроить мониторинг».
- Письма уходят (SMTP, DKIM, SPF — для нового IP).
- Webhooks от внешних сервисов приходят на новый IP.
- Сертификаты живые и обновляются.
Подводные камни
SMTP и репутация IP
Если ты сам шлёшь почту с сервера, новый IP не имеет репутации. Письма могут уходить в спам. Решение: либо использовать внешний SMTP-сервис (Postmark, Resend), либо заранее греть IP. На больших объёмах рассылок переезд почтового сервера — это отдельный квест.
SPF/DKIM/DMARC
В DNS-зоне записи SPF могут содержать старый IP. Перед миграцией проверь:
dig TXT example.ru +short
dig TXT _dmarc.example.ru +shortЕсли в SPF указан конкретный IP старого сервера — обнови до миграции, иначе отправка почты сломается.
Несовместимость версий Postgres
Логическая репликация требует, чтобы версия source была не старше target. Если ты переезжаешь со старого Postgres 13 на новый 17 — это работает. С 17 на 13 — нет, надо сначала апгрейдить старый.
Webhook providers
Платёжные провайдеры, мессенджеры, почтовые сервисы — у всех есть webhook на твой URL. Они кэшируют DNS на разное время. После переезда я ловил «недоставленные webhook» от одного банка ещё пять минут после смены IP, потому что у них кэш был агрессивный.
Сертификаты с фиксированным CN
Если у тебя коммерческий сертификат с конкретным IP в SAN, его придётся перевыпускать. Let's Encrypt такого не делает — не проблема.
WireGuard и VPN-доступ
Если у тебя VPN на этот сервер, IP сменится. Убеди коллег обновить конфиги до миграции, иначе пара часов после переезда они будут стучаться в никуда.
Откат
Если что-то пошло не так, откат через DNS:
- Запись DNS возвращаешь на старый IP.
- Старый сервер из read-only переводишь обратно в полный режим.
- Подписку на новом сервере откатываешь, чтобы данные не разошлись.
- Разбираешься, что не так, готовишь план следующей попытки.
Откат при низком TTL занимает 1–5 минут. Главное — не паниковать и иметь чек-лист «как откатываемся», который написан до миграции, а не в момент инцидента.
Шпаргалка
- За сутки — снизить TTL.
- За день-два — поднять копию окружения, dump БД, начать репликацию.
- В день миграции — финальный rsync, остановка записи, переключение DNS.
- Сразу после — проверка работоспособности, бэкап на новом сервере.
- Через сутки — выключение старого, если новый стабильно держит нагрузку.
Переезд — задача, которую делаешь одной командой только в учебниках. В реальности она занимает день полноценной работы и пару дней наблюдения. Но если подойти системно, downtime сводится к секундам, которые пользователи даже не замечают.