Перенос WordPress на новый сервер без простоя

Опубликовано 14 мин. чтения

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

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

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

Порядок действий, при котором простоя не будет

Старый сайт остаётся в сети до последнего момента. Ничего не выключается и ничего не удаляется. Новый сервер работает параллельно, проходит проверку и принимает нагрузку только после того, как переключена DNS-запись.

  1. Снизить TTL записей A и AAAA до 300 секунд, минимум за 24-48 часов до переезда.
  2. Подготовить новый сервер: веб-сервер, PHP, база данных, пользователи, каталоги.
  3. Скопировать файлы, скопировать базу данных.
  4. Выпустить сертификат для домена, хотя DNS ещё указывает на старый сервер.
  5. На своём компьютере поправить файл hosts и пройти по сайту под настоящим доменом.
  6. Незадолго до переключения выполнить дельта-синхронизацию, чтобы забрать последние изменения.
  7. Переключить DNS. Старый сервер оставить работать ещё несколько дней.

Единственное место, где теоретически возникает разрыв, — это шаг 6. Насколько коротким он окажется, решает шаг 1.

Снизить TTL прежде всего остального

TTL сообщает резолверам по всему миру, как долго им разрешено держать ответ в кэше. Если там стоит 86400, резолвер может отдавать ваш старый IP ещё 24 часа после переключения. И вот что часто упускают из виду: сниженный TTL начинает действовать только после того, как истечёт старый. При переходе с 86400 на 300 может пройти целые сутки, прежде чем короткое значение узнают все резолверы.

Поэтому это первое действие, а не последнее. Утилита dig ни в одном дистрибутиве не входит в базовую поставку, а в этом руководстве она нужна постоянно, поэтому установите её сразу:

apt install -y bind9-dnsutils

В Debian 11, Ubuntu 22.04 и Ubuntu 24.04 пакет всё ещё называется dnsutils. Начиная с Debian 12 dnsutils — лишь заглушка, которая ссылается на bind9-dnsutils, а в Debian 13 и вовсе чисто виртуальный пакет без собственной версии. Работают оба варианта, потому что apt сам подставляет единственного поставщика, но именно bind9-dnsutils останется актуальным именем. После установки проверьте текущее значение:

dig +noall +answer example.com A
dig +noall +answer example.com SOA

Число во втором столбце ответа A — это остаток TTL. У авторитативного сервера спрашивайте напрямую, иначе вы увидите только остаток из кэша вашего резолвера:

dig @a.ns14.net example.com A +noall +answer

После переезда верните TTL обратно наверх, 3600 или больше. Постоянно короткие TTL создают лишнюю нагрузку и удлиняют сбои вашего DNS-провайдера.

Правильно укомплектовать новый сервер

Именно здесь возникает большинство неприятных сюрпризов, потому что дистрибутив на новом сервере поставляет не то же самое, что старый. Все команды установки пакетов в этом руководстве рассчитаны на Debian или Ubuntu; в AlmaLinux, Rocky Linux и Oracle Linux нет apt, а имена пакетов отличаются. По состоянию на июль 2026 года картина такая:

СистемаPHPБаза данныхnginx
Debian 138.4MariaDB 11.8 (без mysql-server)1.26.3
Debian 128.2MariaDB 10.111.22.1
Ubuntu 24.048.3MySQL 8.0.46 или MariaDB 10.111.24.0
Ubuntu 22.048.1MySQL 8.0.46 или MariaDB 10.61.18.0

Отсюда следуют два вывода. Первый: в Debian вы не получите mysql-server, там всегда работает MariaDB. Если старый сайт работал на MySQL 8, переезд на Debian одновременно означает смену СУБД, с очень конкретной ловушкой, о которой ниже. Второй: скачок с PHP 8.1 на 8.4 не проходит сам собой. Старые темы и плагины отвечают на удалённые функции сообщением Fatal error: Uncaught Error: Call to undefined function или белой страницей. Если старая установка работала на PHP 8.1 и связывать переезд с обновлением PHP не хочется, спокойнее выбрать Ubuntu 22.04 либо версию PHP из стороннего репозитория. Чего в таблице сознательно нет, так это Debian 11: там php-cli всё ещё даёт PHP 7.4.33, то есть версию без поддержки безопасности и ниже того, что рекомендует WordPress. Как цель переезда Debian 11 отпадает, разве что вы заранее подключите репозиторий Sury.

Базовые пакеты для типичной установки с nginx и PHP-FPM:

apt update
apt install -y nginx php-fpm php-mysql php-xml php-curl php-mbstring php-zip php-gd php-intl
apt install -y mariadb-server mariadb-client rsync curl

Подробности по веб-серверу есть в руководстве по nginx, базовая защита СУБД описана в статье Защита MariaDB и MySQL. Если сервер только что создан, стоит сначала заглянуть в чек-лист для новых root-серверов.

Создайте базу данных и пользователя с той же кодировкой, что и на источнике:

CREATE DATABASE wp_new CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wp_new'@'localhost' IDENTIFIED BY 'длинный-пароль';
GRANT ALL PRIVILEGES ON wp_new.* TO 'wp_new'@'localhost';
FLUSH PRIVILEGES;

Перенести файлы, не сломав права доступа

Копирование идёт напрямую с сервера на сервер, а не через домашнее подключение. Проще всего сделать это через rsync со старого сервера на новый, используя SSH-ключ вместо пароля:

rsync -az --delete --exclude 'wp-content/cache/' --exclude 'wp-content/uploads/backup*' \
  -e 'ssh -p 22' /var/www/html/ root@new.ip:/var/www/html/

Первый проход может идти долго, и это нормально. Именно поэтому его выполняют за несколько дней до переключения, а прямо перед переключением просто повторяют ту же команду, которая передаст только различия.

После копирования нужно привести в порядок владельца и права, потому что UID на разных системах отличаются, а rsync передаёт числа, а не имена:

chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
chmod 640 /var/www/html/wp-config.php

Перед первым запуском убираются два файла, если они есть: wp-content/object-cache.php и wp-content/advanced-cache.php. Оба они — drop-in кэширующих плагинов, которые указывают на сокет Redis или Memcached, а на новом сервере такого сокета ещё нет. Результатом была бы белая страница без внятного сообщения.

Перенос базы данных и ловушка с коллацией

Дамп снимается либо через WP-CLI, либо классическим способом. Преимущество WP-CLI в том, что кодировку и префикс он берёт из wp-config.php:

cd /var/www/html
wp db export /root/wp-dump.sql --add-drop-table

Без WP-CLI: в актуальных системах с MariaDB инструмент называется mariadb-dump. Старое имя mysqldump начиная с MariaDB 11.0 всего лишь устаревший симлинк, который отвечает предупреждением Deprecated program name. It will be removed in a future release:

mariadb-dump --single-transaction --default-character-set=utf8mb4 wp_old > /root/wp-dump.sql

Теперь то место, на котором переезд с Ubuntu и MySQL 8 на Debian с MariaDB регулярно спотыкается. MySQL 8 по умолчанию использует коллацию utf8mb4_0900_ai_ci, которой в MariaDB нет вовсе. Импорт прерывается сообщением:

ERROR 1273 (HY000) at line 42: Unknown collation: 'utf8mb4_0900_ai_ci'

Правка выполняется до импорта, прямо в дампе:

sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g; s/utf8mb4_0900_as_cs/utf8mb4_unicode_ci/g' /root/wp-dump.sql
grep -c utf8mb4_unicode_ci /root/wp-dump.sql

После этого импортируйте:

mariadb --default-character-set=utf8mb4 wp_new < /root/wp-dump.sql

Если после импорта повсюду стоит Ð´Ð»Ñ вместо для, значит кодировка потерялась при снятии дампа или при импорте. Это чинится не поиском и заменой, а повторным дампом и импортом с параметром --default-character-set=utf8mb4. Именно поэтому старая база данных на этом этапе всё ещё существует в нетронутом виде.

Затем поправьте доступы в wp-config.php на новом сервере: DB_NAME, DB_USER, DB_PASSWORD и, что особенно важно, DB_HOST, если старая установка указывала на удалённый сервер базы данных. Если там останется старый адрес, вы увидите Error establishing a database connection, хотя локально всё работает. Если соединение не устанавливается несмотря на верные данные, поможет статья Устранение ошибки Access denied for user.

Замена домена и почему SQL-replace разносит сайт

Сначала хорошая новость: если меняется только сервер, а домен остаётся прежним, в базе данных заменять вообще ничего не нужно. Это одна из причин, по которым проверка через файл hosts лучше проверки через временный домен. Замена нужна лишь тогда, когда домен действительно меняется, когда переезд совмещается с переходом с http на https или когда вы всё же работали с тестовым доменом и потом должны заменить всё обратно.

А теперь причина, по которой это никогда не делают простой SQL-командой. WordPress хранит настройки, параметры виджетов и конфигурации тем в виде сериализованных массивов PHP. URL записан там не сам по себе, а с указанием длины в байтах перед ним:

s:19:"https://example.com"

Если заменить example.com через UPDATE ... REPLACE() на new-domain.ru, строка станет длиннее, а число 19 останется прежним. После этого PHP уже не сможет распаковать массив, unserialize() вернёт false, и соответствующая настройка пропадёт. Типичные симптомы: исчезли виджеты, сбросились опции темы, опустели пункты меню, а в логах появилось Notice: unserialize(): Error at offset.

WP-CLI решает задачу правильно: он распаковывает данные, выполняет замену и записывает их обратно с исправленной длиной. Всегда начинайте с --dry-run:

wp search-replace 'https://old-domain.ru' 'https://new-domain.ru' --all-tables --precise --dry-run --report-changed-only

Что делают отдельные ключи: --all-tables затрагивает и таблицы, которые не следуют префиксу WordPress, например от плагинов магазина или форм. --precise заставляет обрабатывать данные средствами PHP, а не SQL: это медленнее, зато сериализованные данные обрабатываются надёжно. --report-changed-only сокращает вывод до таблиц с реальными совпадениями.

Если предпросмотр выглядит правдоподобно, запустите ту же команду без --dry-run. Дальше идёт проход, который почти все руководства пропускают: конструкторы страниц вроде Elementor хранят содержимое в базе в формате JSON, а там слэши экранированы. Замена https://old-domain.ru попросту не найдёт https:\/\/old-domain.ru. Поэтому нужен второй проход:

wp search-replace 'https:\/\/old-domain.ru' 'https:\/\/new-domain.ru' --all-tables --precise --report-changed-only

Для Elementor после этого нужно в админке в разделе «Инструменты» пересоздать файлы CSS, иначе сгенерированные таблицы стилей продолжат указывать на старый домен.

Две вещи, до которых search-replace не добирается: константы в wp-config.php и мультисайт. Если там стоит define('WP_HOME', 'https://old-domain.ru');, это перекрывает любое значение из базы, и вы будете часами искать не в том месте. Для мультисайта дополнительно нужен ключ --network, а домены придётся править в wp_blogs и wp_site.

И предупреждение про префикс таблиц: не меняйте его во время переезда. Префикс сидит ещё и в имени опции wp_user_roles, и в метаполях пользователя wp_capabilities и wp_user_level. Тот, кто переименует только таблицы, войти в систему ещё сможет, но сразу после этого получит Sorry, you are not allowed to access this page. и останется без администратора.

Выпуск сертификата до того, как DNS указывает на новый сервер

Здесь возникает проблема курицы и яйца. Обычная HTTP-проверка Let's Encrypt требует, чтобы доменное имя уже указывало на проверяемый сервер. Но на этом этапе оно ещё указывает на старый сервер, ведь он должен продолжать отдавать сайт. Тот, кто всё равно запустит certbot --nginx, получит:

Certbot failed to authenticate some domains (authenticator: nginx).
Invalid response from http://example.com/.well-known/acme-challenge/...: 404

Чистый выход — проверка через DNS. Она проверяет TXT-запись _acme-challenge.example.com и не интересуется тем, куда указывает A-запись:

certbot certonly --manual --preferred-challenges dns -d example.com -d www.example.com

Certbot покажет значение, которое нужно внести в вашу DNS-зону как TXT-запись. Перед подтверждением обязательно проверьте сами, отдаёт ли зона эту запись, иначе вы впустую потратите одну попытку:

dig +short TXT _acme-challenge.example.com

Для wildcard-доменов и для автоматизации путь через DNS-API описан в статье про wildcard-сертификат. Ручной вариант сам себя не продлевает, поэтому после переключения DNS переведите выпуск на обычную HTTP-проверку. С этого момента она заработает, потому что домен уже указывает на новый сервер.

Проверка через файл hosts, как настоящий посетитель

Теперь часть, которая отличает «должно работать» от «работает». Вы направляете на новый сервер только свой собственный компьютер, тогда как весь остальной мир по-прежнему видит старый сайт.

В Linux и macOS файл /etc/hosts редактируется от root, в Windows это C:\Windows\System32\drivers\etc\hosts в редакторе, запущенном от имени администратора. Добавьте строку с IP нового сервера:

203.0.113.10 example.com www.example.com

После этого очистите кэш DNS, иначе изменение подействует не сразу:

resolvectl flush-caches

В Windows это ipconfig /flushdns, в macOS sudo dscacheutil -flushcache и следом sudo killall -HUP mDNSResponder. Проверьте, сработало ли:

getent hosts example.com

Ловушка, которая стоит многих часов: Firefox с включённым DNS поверх HTTPS игнорирует файл hosts. В Mozilla это явно пометили как «wontfix». Вы продолжаете видеть старый сайт и считаете проверку проваленной, хотя новый сервер давно отвечает правильно. Поэтому в тестовом браузере отключите шифрованное разрешение имён, а ещё лучше проверяйте независимо от браузера. Утилита curl умеет переопределять разрешение имён для каждого вызова, вообще без файла hosts:

curl -sI --resolve example.com:443:203.0.113.10 https://example.com/

Что стоит пройти в этом состоянии: главную страницу, минимум две внутренние страницы, запись с изображениями, вход на /wp-login.php, админку, форму обратной связи, а для магазина тестовый товар вплоть до оформления заказа. Изображения заслуживают особого внимания, потому что отсутствующие загрузки в админке в глаза не бросаются.

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

Переключение: дельта-синхронизация и DNS

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

  1. На старом сервере включить режим обслуживания или хотя бы ненадолго остановить приём заказов и комментариев.
  2. Заново синхронизировать файлы, на этот раз это займёт секунды.
  3. Снять свежий экспорт базы данных и импортировать его на новом сервере.
  4. Очистить кэши: wp cache flush и wp rewrite flush --hard.
  5. Ещё раз проверить через curl обращением к новому IP.
  6. Переключить записи A и AAAA.

Не забудьте про запись AAAA. Если она останется на старом адресе IPv6, все посетители с IPv6 будут по-прежнему попадать на старый сервер, а посетители с IPv4 увидят новый сайт. Получается ровно та картина, когда два человека сидят рядом и видят разные сайты.

Старый сервер оставьте работать минимум неделю и ничего на нём не меняйте. Это ваш путь отката и ваш архив.

Как убедиться, что переезд действительно удался

Не «сайт открывается», а измеримые доказательства:

dig +short A example.com
dig +short AAAA example.com
curl -sI https://example.com/ | head -n 12

В заголовках не должно быть 301 на старый домен или на http://. Затем спросите у базы данных, какой адрес WordPress сам считает правильным:

wp option get siteurl
wp option get home

Надёжная проверка на пропущенные остатки — это прогон поиска в холостом режиме. Если он сообщает о нуле замен, старый адрес действительно нигде больше не прописан:

wp search-replace 'old-domain.ru' 'old-domain.ru' --all-tables --dry-run --report-changed-only

К этому добавьте проверку сертификата, включая срок действия и покрытые имена:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject

И наконец лог ошибок веб-сервера, пока вы пять минут кликаете по сайту. Если там не появляется ничего нового, дело сделано. Если же что-то всё-таки виснет, чаще всего помогает статья Устранение ошибки 502 Bad Gateway: обычно причина в том, что сокет PHP-FPM в блоке nginx всё ещё содержит путь от старой версии PHP.

Если что-то пошло не так: путь отката

Путь отката состоит из одного действия, и именно поэтому шаг 1 был так важен: вернуть записи A и AAAA на старый IP. При TTL в 300 секунд большинство посетителей в течение пяти минут снова окажется на старом сервере, который всё это время работал без изменений.

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

Если возвращаться придётся позже, экспортируйте с нового сервера только затронутые таблицы и залейте их на старый, вместо того чтобы возвращать базу данных целиком. Чистый откат из старой полной резервной копии сотрёт всё, что произошло за это время.

Сообщения об ошибках дословно

Error establishing a database connection
Доступы или DB_HOST в wp-config.php не соответствуют новому серверу. Часто там всё ещё стоит IP внешнего сервера базы данных вместо localhost.

ERROR 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
Дамп из MySQL 8 импортируется в MariaDB. Замените коллацию в дампе через sed на utf8mb4_unicode_ci.

Error: This does not seem to be a WordPress installation.
WP-CLI запущен не в том каталоге, либо файлы лежат не там, где вы предполагаете. Работайте с ключом --path=/var/www/html.

ERR_TOO_MANY_REDIRECTS
Почти всегда это противоречие между siteurl в базе данных, константой в wp-config.php и перенаправлением на веб-сервере. Кроме того, это касается установок за прокси, которые не получают статус HTTPS и поэтому бесконечно перебрасывают с http на https и обратно.

Главная страница открывается, все внутренние страницы отдают 404
Классика при переходе с Apache на nginx. Файл .htaccess с правилами постоянных ссылок nginx игнорирует, там нужен try_files $uri $uri/ /index.php?$args; в блоке location.

Загруженный файл превышает директиву upload_max_filesize в php.ini.
Конфигурация PHP не переехала вместе с сайтом. Установите upload_max_filesize, post_max_size и memory_limit в значения со старого сервера.

Белая страница, в логе ничего нет
Чаще всего виноват drop-in, указывающий в пустоту: wp-content/object-cache.php или advanced-cache.php. Переименуйте и перезагрузите страницу.

У переезда, который проходит именно так, нет промежутка без сайта. Единственный заметный момент — смена DNS, а её длительность вы задали сами двумя днями раньше.

Частые вопросы

Сколько времени нужно, чтобы переключение DNS подействовало везде?
Это зависит исключительно от TTL, который действовал до переключения. При 300 секундах практически все резолверы переходят на новый адрес за пять-десять минут. При 86400 это может занять целые сутки. Поскольку сниженный TTL начинает работать только после того, как истечёт старый, снижайте его за 24-48 часов до переезда.
Почему нельзя просто заменить домен в базе данных обычной SQL-командой?
WordPress хранит многие настройки в виде сериализованных массивов PHP, где перед каждой строкой указана её длина в байтах, например s:19:"https://example.com". REPLACE() меняет текст, но не длину. После этого PHP уже не может распаковать массив, и настройка теряется. WP-CLI распаковывает данные, выполняет замену и сериализует заново, поэтому правильный путь — wp search-replace.
Нужно ли вообще менять домен, если меняется только сервер?
Нет. Если домен остаётся тем же, в базе данных ничего не меняется. Замена нужна только при настоящей смене домена, при одновременном переходе с http на https или в том случае, если вы работали через тестовый домен и потом должны заменить всё обратно.
Как получить сертификат Let's Encrypt, если домен ещё указывает на старый сервер?
Через проверку DNS вместо проверки HTTP: certbot certonly --manual --preferred-challenges dns -d example.com. Проверяется TXT-запись под _acme-challenge, а A-запись при этом роли не играет. После переключения DNS перейдите на автоматическую HTTP-проверку, чтобы продление шло без ручной работы.
Я поправил файл hosts, но по-прежнему вижу старый сайт. В чём причина?
Либо кэш DNS операционной системы ещё тёплый, тогда помогут ipconfig /flushdns, resolvectl flush-caches или dscacheutil -flushcache. Либо ваш браузер сам разрешает имена через DNS поверх HTTPS. Firefox в этом режиме файл hosts заведомо игнорирует. Надёжна проверка через curl с ключом --resolve, которая полностью обходит браузер.
Какой самый быстрый путь отката, если после переключения что-то не работает?
Вернуть записи A и AAAA на старый IP. Старый сервер ведь продолжает работать без изменений. При коротком TTL большинство посетителей вернётся туда за считанные минуты. Подвох в том, что данные, появившиеся за это время только на новом сервере, например заказы, на старом будут отсутствовать. Поэтому промежуток держат коротким с помощью режима обслуживания и решают быстро.

WordPress Перенос сервера DNS WP-CLI MariaDB Let's Encrypt VPS