Перенос сайта: домен и хостинг
Домен и хостинг
У каждого сайта две части, и они могут принадлежать разным компаниям:
- домен — имя, которое вы набираете в браузере, зарегистрированное у регистратора;
- сайт — его страницы, картинки, скрипты и база данных, которые лежат у хостинг-провайдера.
Одна компания может продавать и то и другое, но не обязана. До переезда выясните по каждой части, у кого она, на чьё имя записана и как вы получаете доступ. Практические руководства по двум общим хостингам из примера: Beget и DreamHost. Инструменты, которые понадобятся: curl, SSH, git.
Сравните ещё до оплаты, сколько вы платите за сайт сейчас и сколько будете платить у нового хостера: разница бывает в несколько тысяч рублей в год. Домен может быть записан и на человека, и на ООО, а хостинг продаваться совсем другой компанией — проверьте, что домен записан на вас, а не на провайдера, который только перепродаёт чужие домены.
Перенос домена
Домен записан на человека или на компанию. Убедитесь, что он ваш. Регистратор и статус домена открыты на lookup.icann.org и в выводе whois:
whois example.com
[Querying whois.iana.org:43 'example.com'] [whois.iana.org] % IANA WHOIS server % for more information on IANA, visit http://www.iana.org % This query returned 1 object domain: EXAMPLE.COM organisation: Internet Assigned Numbers Authority created: 1992-01-01 source: IANA
example.com — зарезервированное имя, поэтому ответ останавливается на IANA и регистратора в нём нет. У настоящего домена ищите Registrar:, Registrar WHOIS Server:, Domain Status: и Registry Expiry Date:. Контактный email должен быть тем, который вы читаете: на него регистратор присылает коды для переноса.
Сервисов WHOIS много, и обновляют данные они с разной периодичностью, поэтому проверьте сразу несколько. Регистраторами называются организации, у которых есть право регистрировать домены, и не каждый хостинг-провайдер им является: в России один из крупнейших — reg.ru.
Три вопроса к DNS отвечают, где сейчас живут сайт и его почта. NS называет серверы, которые отвечают за домен, A — адрес сайта, MX — почтовые серверы:
dig +short NS example.com
ns2.dreamhost.com. ns1.dreamhost.com. ns3.dreamhost.com.
dig +short A example.com
203.0.113.10
dig +short MX example.com
Запрос MX ничего не печатает: этот домен не получает почту, поэтому почтовой записи, которую надо сохранить, у него нет.
Запишите ответы. После переезда NS и A должны указывать на новый хостинг, а MX — по-прежнему на работающий почтовый сервер. Что значат записи: DNS.
Чтобы перенести домен вроде .com к другому регистратору, нужен его код переноса (auth code, EPP code) от старого регистратора, а сам домен должен быть разблокирован. Обычно он не может переехать в первые 60 дней после регистрации или прошлого переноса. У страновых доменов свои правила; спросите регистратора. Учтите ещё, сколько стоит продление в год сейчас и у нового регистратора. Для переезда одного хостинга код переноса не нужен: домен остаётся у своего регистратора, меняются только его записи DNS.
Снизить TTL
Ответы DNS кешируются на время жизни записи, TTL. Запись с TTL в один час ещё час после переключения отправляет посетителей на старый сервер. Снизьте TTL за день до переезда у DNS-провайдера, который держит записи сейчас. Достаточно значения 300 секунд:
dig example.com A
example.com. 59 IN A 203.0.113.10
TTL — это число во втором столбце ответа. Поставьте 300 для записей A, AAAA и CNAME сайта. Подождите не меньше старого TTL, прежде чем переключать: так старое значение уйдёт из всех кешей. На Beget записи лежат в панели управления; панель DNS-провайдера делает то же самое, когда домен использует его серверы имён.
Копирование файлов
Для файлов нужен доступ к хостингу: панель управления, или FTP, или SFTP с адресом, именем пользователя и паролем, или SSH. Копируйте по SSH: так сохраняются отметки времени, а права можно выставить в том же проходе:
rsync -av --chmod=D755,F644 ~/site/ dh_user@<server>.dreamhost.com:~/example.com/
-a сохраняет время, ссылки и права. --chmod=D755,F644 затем перекрывает права в копии: каждая папка становится 755, каждый файл — 644. Без него права локальных файлов переезжают как есть. В рабочей копии часто папки 700, а файлы 600, и Apache, который работает под другим пользователем, не может их читать. Именно это случилось в проблеме 3 примера. После загрузки обе проверки должны напечатать 0:
find ~/example.com -type d ! -perm 755 | wc -l
find ~/example.com -type f ! -perm 644 | wc -l
Выкладка, которая копирует только отслеживаемые git файлы, пропускает картинки и другие игнорируемые файлы. Сначала перечислите игнорируемые файлы, затем скопируйте этот список:
cd ~/site
git ls-files --others --ignored --exclude-standard > /tmp/untracked.txt
wc -l /tmp/untracked.txt
rsync -av --chmod=D755,F644 --files-from=/tmp/untracked.txt ./ dh_user@<server>.dreamhost.com:~/example.com/
Проверьте, что каждая картинка на новом хостинге отвечает 200: пропавшая картинка — это сломанная страница, которую не увидит ни одна проверка HTML. Как хранить такой список в репозитории: Изображения вне git: список файлов в репозитории.
Перенести сайт может и сам новый хостер: часто достаточно сообщить ему данные доступа к панели или по FTP, и файлы с базой он скопирует сам. Спросите об этом до оплаты: у одного провайдера перенос входит в тариф, у другого это отдельная услуга. FTP-клиент вам, скорее всего, не понадобится.
Копирование базы данных
Сайту с MySQL или MariaDB нужны ещё и данные. Выгрузите их на старом хостинге одной транзакцией, вместе с процедурами и триггерами:
mysqldump --single-transaction --routines --triggers -u user -p example > ~/backups/example-$(date +%F).sql
--single-transaction делает согласованный снимок, не блокируя таблицы нагруженного сайта. --routines и --triggers сохраняют хранимые процедуры и триггеры, которые обычный дамп выбрасывает. С -p команда спрашивает Enter password: и больше ничего не печатает при успехе. Скопируйте файл на новый хостинг:
scp ~/backups/example-2026-11-07.sql dh_user@<server>.dreamhost.com:~/backups/
Создайте базу и её пользователя в панели нового хостинга, затем загрузите в неё файл:
mysql -u user -p example < ~/backups/example-2026-11-07.sql
С -p команда спрашивает Enter password: и больше ничего не печатает при успехе.
Посчитайте таблицы с обеих сторон; числа должны совпасть:
mysql -u user -p -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'example';"
Команды одинаковы для MySQL и MariaDB. База с другим именем или другой пользователь на новом хостинге требуют поправить дамп или загрузить его под новым именем; и то и другое видно в панели.
Проверка до DNS
Не меняйте DNS первым и не отлаживайте потом. Новый сервер отвечает за домен, как только настроен его веб-сервер, даже пока публичный DNS указывает на старый. Принудительно направьте соединение на новый адрес через --resolve:
curl -sS --resolve example.com:443:203.0.113.10 -o /dev/null -w "%{http_code} %{remote_ip}\n" https://example.com/
200 203.0.113.10
--resolve example.com:443:203.0.113.10 соединяется с 203.0.113.10, но отправляет example.com в заголовке Host и в рукопожатии TLS, поэтому сертификат и каждый виртуальный хост совпадают с настоящим именем. %{remote_ip} в выводе показывает, какой сервер ответил. Проверьте главную и несколько внутренних страниц, http и https, и один адрес, который должен отвечать 404.
Браузеру вместо этого нужна строка в /etc/hosts. Добавьте её редактором от root и удалите после проверки:
sudo vi /etc/hosts
203.0.113.10 example.com www.example.com
Теперь обычный адрес в браузере открывает новый сервер. Проверьте сертификат, редирект на https и форму, которая пишет в базу данных. Уберите строку, когда закончите, иначе браузер продолжит видеть новый сервер и после ошибки.
Смена DNS
Когда новый сайт отвечает правильно, смените серверы имён у регистратора или записи A у DNS-провайдера на новый хостинг. Если домен остаётся у того же регистратора и меняется только хостинг, обычная правка — одна запись A. Если переезжает весь DNS, регистратор получает новые серверы имён, например четыре у Beget или три у DreamHost.
Следите за изменением из публичного резолвера, а не с сервера, который его сделал:
dig +short NS example.com
ns2.dreamhost.com. ns1.dreamhost.com. ns3.dreamhost.com.
dig +short A example.com @8.8.8.8
203.0.113.10
curl -sS -o /dev/null -w "%{http_code} %{remote_ip}\n" https://example.com/
200 203.0.113.10
Адрес в %{remote_ip} должен быть адресом нового сервера. Держите старый хостинг оплаченным и работающим неделю: часть резолверов и браузеров кеширует старый адрес на старый TTL, и откат тогда — это одна правка DNS.
Чек-лист
| Задача | Команда или проверка |
|---|---|
| Сравнить цену за сайт | сколько платите сейчас и сколько будете платить у нового хостера, в год |
| Посмотреть домен | whois example.com, lookup.icann.org |
| Записать регистратора, email домена и хостера | кто где, на чьё имя записано и как вы входите |
| Записать текущий DNS | dig +short NS example.com, dig +short A example.com, dig +short MX example.com |
| Снизить TTL за день | dig example.com A, затем поставить 300 у DNS-провайдера |
| Получить код переноса и разблокировать домен | только когда меняется регистратор |
| Скопировать файлы с безопасными правами | rsync -av --chmod=D755,F644 ~/site/ user@host:~/example.com/ |
| Скопировать неотслеживаемые файлы | git ls-files --others --ignored --exclude-standard, затем rsync по списку |
| Выгрузить и загрузить базу данных | mysqldump --single-transaction --routines --triggers, затем mysql ... < dump.sql |
| Проверить новый сервер до DNS | curl --resolve example.com:443:<new-ip> https://example.com/ |
| Сменить DNS | серверы имён у регистратора или запись A у DNS-провайдера |
| Проверить, что новый адрес отвечает | dig +short A example.com @8.8.8.8, curl -w "%{remote_ip}" |
| Проверить картинки и внутренние страницы | curl -o /dev/null -w "%{http_code}" для каждой |
| Найти файлы, которые уводит редиректом правило для страниц | 301 на путях картинок и скриптов в журнале доступа: awk '$9 == 301 && $7 ~ /\.(jpe?g|png|webp|gif|svg|js|css)$/ {print $7}' ~/logs/example.com/https/access.log | sort | uniq -c (проблема 6) |
Как выбрать новый: Какой хостинг выбрать. Правила, которые часто ломаются при переезде: Создание правил в .htaccess.
Пример: с Beget на DreamHost
Это настоящий переезд, имена изменены: домен — example.com, логин на Beget — user с домашней папкой /home/u/user, логин на DreamHost — dh_user, сервер — <server>.dreamhost.com. Сайт — написанный вручную сайт на PHP без базы данных. Большую часть времени заняли шесть проблем; каждая ниже — это симптом, команда, которая его показывает, вывод и исправление.
| Проблема | Симптом | Причина |
|---|---|---|
| 1 | 500 на каждый запрос | php_flag без mod_php |
| 2 | каждый URL перенаправляется сам на себя | правило https проверяет только заголовок прокси |
| 3 | около 90 страниц отвечают 404 или 403 | папки 700 и файлы 600 |
| 4 | старые URL отвечают 404 на новом хостинге | устаревшие копии на старом хостинге, а не в репозитории |
| 5 | пропали картинки | загрузка по git пропускает игнорируемые файлы |
| 6 | картинки и скрипты из одной папки отвечают 301 | правило для PATH_INFO подходит и к папке с именем .php |
Проблема 1: 500 на каждый запрос
Симптом. Первый же запрос, который записал в лог новый сервер, уже ответил 500, главная страница в том числе. Встроенная страница Apache сообщала, что сервер обнаружил внутреннюю ошибку. Заголовки ответа:
curl -sS -D - -o /dev/null https://example.com/
HTTP/2 500 content-length: 533 content-type: text/html; charset=iso-8859-1 server: Apache
Команда, которая это показывает. В логе ошибок названы файл и директива:
tail -n 1 ~/logs/example.com/https/error.log
[Fri Nov 06 23:29:54 2026] [example.com] [alert] [client 203.0.113.7:37118] [pid 3064457] config.c(2045): /home/user/example.com/.htaccess: Invalid command 'php_flag', perhaps misspelled or defined by a module not included in the server configuration
Почему. Beget запускает PHP как mod_php, где php_flag и php_value работают. DreamHost запускает PHP отдельным процессом FastCGI под управлением mod_fcgid и mod_php не загружает, поэтому Apache не знает этих слов: Почему php_flag отвечает 500. Полный разбор — в статье 500 на каждой странице после переезда.
Исправление. Сначала сделайте копию файла:
mkdir -p ~/backups
cp -p .htaccess ~/backups/example.com.htaccess.$(date +%Y%m%d-%H%M%S)
Оберните двенадцать строк в IfModule, чтобы хостинг без mod_php их пропускал:
vi .htaccess
<IfModule mod_php.c> php_flag display_errors off php_flag log_errors on # остальные десять строк остаются внутри этого блока </IfModule>
Затем положите те же настройки в файл, который читает DreamHost, и перезапустите PHP-процесс:
vi ~/.php/8.5/phprc
display_errors = Off log_errors = On error_reporting = -1
pkill -x php85.cgi
Защитные блоки в общем виде: IfModule. Проверка, которая отличает mod_php от FastCGI: Какой у вас сервер.
Проблема 2: каждый URL перенаправлялся сам на себя
Симптом. Когда 500 не стало, каждый адрес отвечал 301, а целью был он сам. Один запрос:
curl -sS -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.com/
301 https://example.com/
Команда, которая это показывает. Если пойти по редиректу, виден цикл:
curl -sS -L -o /dev/null https://example.com/
curl: (47) Maximum (50) redirects followed
Почему. Старое правило https проверяло только заголовок, который присылает фронтенд Beget:
RewriteCond %{HTTP:X-Forwarded-Proto} !https RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
DreamHost завершает TLS в самом Apache и такого заголовка не присылает, поэтому «не https» было верно для каждого запроса, включая https.
Исправление. Добавьте условие на состояние TLS, которое видит сам Apache:
vi .htaccess
RewriteCond %{HTTPS} off RewriteCond %{HTTP:X-Forwarded-Proto} !https RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
На DreamHost %{HTTPS} равно on для https, и первое условие останавливает правило. За старым прокси %{HTTPS} всегда off, и решает заголовок. Браузеры кешируют 301: после такого цикла проверяйте в приватном окне. Вся история — в статье Переезд на DreamHost: цикл; как написать правило для обоих видов хостинга: Redirect 301 или пара.
Проблема 3: около 90 страниц отвечали 404 или 403
Симптом. Страницы, которые работали на Beget, на DreamHost отвечали 403 Forbidden или 404 Not Found. Файлы были на месте, пути тоже.
Команда, которая это показывает. Сравните права папки и её дочерней папки:
stat -c '%a %U:%G %n' dev/python dev/python/files
755 dh_user:pg000000 dev/python 700 dh_user:pg000000 dev/python/files
Почему. У 69 папок в локальной копии были права 700, а у примерно 450 файлов — 600 или 700. rsync скопировал эти права, как и zip-архив, распакованный на сервере. На новом хостинге Apache, который работает под другим пользователем, не мог войти в папки и прочитать файлы.
Исправление. Сохраните старые права, затем откройте папки и файлы на сервере:
find ~/example.com -printf '%m %p\n' > ~/backups/example.com.modes.$(date +%Y%m%d-%H%M%S)
find ~/example.com -type d -exec chmod 755 {} +
find ~/example.com -type f -exec chmod 644 {} +
Исправьте локальную копию так же, иначе следующая загрузка вернёт права обратно. С этого момента копируйте с rsync --chmod=D755,F644, как в разделе Копирование файлов. Сторона DreamHost: SSH и права файлов; контейнер, который воспроизводит 403: проверка 5.
Проблема 4: старые URL отвечали 200 на Beget и 404 на DreamHost
Симптом. Часть адресов, которые ещё работали на старом хостинге, на новом отвечала 404. Этих старых адресов в репозитории не было вовсе.
Команда, которая это показывает. У старого сайта были папки верхнего уровня /centos/, /rocky/, /ubuntu/ и /debian/. Их страницы в репозитории переехали в /linux/..., но старый хостинг по-прежнему отвечал 200 на старые пути, потому что загрузка никогда не удаляет файлы на сервере. На новом хостинге был только репозиторий. Его лог доступа показывал 404:
grep '"GET /centos/network/firewall.php ' ~/logs/example.com/https/access.log
198.51.100.23 - - [07/Nov/2026:00:34:30 -0800] "GET /centos/network/firewall.php HTTP/2.0" 404 5665 "https://www.example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
Почему. Это были устаревшие копии на старом хостинге, а не страницы репозитория. На новом хостинге по старым путям не было ничего.
Исправление. До переезда перечислите, что старый хостинг отдаёт, а репозиторий не содержит. Карта сайта — хорошее начало:
curl -sS https://example.com/sitemap.xml | grep -o "<loc>[^<]*" | cut -c 6- > /tmp/old-urls.txt
wc -l /tmp/old-urls.txt
5523
Проверьте список на новом хостинге после загрузки. Пары правил добавьте после всех точных правил для этих папок, чтобы папка и её страницы уходили в одно новое дерево:
vi .htaccess
RewriteCond %{REQUEST_URI} ^/(centos|rocky|ubuntu|debian)/?$ RewriteRule ^ %{ENV:SCHEME}://%{HTTP_HOST}/linux/%1/ [R=301,L] RewriteCond %{REQUEST_URI} ^/(centos|rocky|ubuntu|debian)/(.*)$ RewriteRule ^ %{ENV:SCHEME}://%{HTTP_HOST}/linux/%1/%2 [R=301,L]
После этого один старый адрес делает один переход в новое дерево:
curl -sS -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.com/centos/network/firewall.php
301 https://example.com/linux/centos/network/firewall.php
Порядок правил и правило одного перехода: Порядок правил. SCHEME задаётся один раз в начале файла правилом E=SCHEME:https; полный блок — в статье пара. Редирект для статического файла, который ещё лежит на диске, на Beget не срабатывает: nginx отдаёт файл первым, как объясняет nginx перед Apache. Когда 301 оставляет страницу, которая должна исчезнуть, используйте [R=404,L]: Безопасные правила, защитные блоки и откат.
Проблема 5: пропали картинки
Симптом. Страницы загружались, но картинки отвечали 404. В локальной копии картинки работали.
Команда, которая это показывает. Посчитайте картинки, которые git отслеживает, и картинки, которые он игнорирует:
git ls-files | grep -c -i -E '\.(jpe?g|png|webp|gif)$'
242
git ls-files --others --ignored --exclude-standard | grep -c -i -E '\.(jpe?g|png|webp|gif)$'
19475
Почему. Файлы картинок игнорируются git. Выкладка, которая копирует только отслеживаемые файлы, их пропускала, а на старом хостинге ещё лежали копии, загруженные годами раньше по FTP.
Исправление. Скопируйте игнорируемые файлы отдельно с тем же перекрытием прав, затем проверьте каждую на новом хостинге:
rsync -av --chmod=D755,F644 ~/site/img/ dh_user@<server>.dreamhost.com:~/example.com/img/
curl -sS -o /dev/null -w "%{http_code} %{size_download}\n" https://example.com/img/cover.jpg
200 180023
Список игнорируемых файлов, который хранится в репозитории, делает следующий переезд повторяемым: Изображения вне git: список файлов в репозитории.
Проблема 6: картинки и скрипты из одной папки отвечали 301
Симптом. На нескольких страницах карточки показывали сломанные картинки, а их скрипты не работали. Файлы лежали на сервере с правильными правами, а журнал ошибок о них молчал.
Команда, которая это показывает. Запросите один из файлов:
curl -sS -o /dev/null -D - https://example.com/.php/img/cards/airport.jpg | grep -i -E '^(HTTP|location|server)'
HTTP/2 301 location: https://example.com/.php server: Apache
Затем выведите все 301 ниже этой папки из журнала доступа:
awk '$9 == 301 && $7 ~ /\/\.php\// {print $7}' ~/logs/example.com/https/access.log | sort | uniq -c
3 /ru/.php/img/cards/actress-800x600.jpg 1 /.php/scripts/top10_script3.js 1 /.php/scripts/top10_script2.js 1 /ao/.php/scripts/top10_script2.js
Причина. Сайт хранит свои PHP-классы, а с ними картинки и скрипты, которые они используют, в папках с именем .php. Правило, которое уводит страницу с PATH_INFO на чистый адрес, использовало шаблон ^(.+\.php)/.+$, а он подходит и к этим папкам. На Beget файлы отдавал nginx раньше, чем Apache читал правило, поэтому ошибка не проявлялась. На DreamHost их отдаёт Apache.
Исправление. Требовать перед .php имя файла:
RewriteCond %{REQUEST_URI} ^(.*[^/]\.php)/.+$
Правило целиком — DreamHost: статические файлы проходят через .htaccess, а тот же запрос к серверам обоих видов, до исправления и после, — в проверке 6 плана проверок Docker.
После всех шести исправлений главная и внутренняя страница отвечали 200, а несуществующий адрес — 404:
for u in http://example.com/ https://example.com/ https://example.com/about/ https://example.com/no-such-page-xyz/; do curl -sS -o /dev/null -w "%{http_code} %{redirect_url} <- $u\n" "$u"; done
301 https://example.com/ <- http://example.com/ 200 <- https://example.com/ 200 <- https://example.com/about/ 404 <- https://example.com/no-such-page-xyz/
Те же проверки, автоматизированные против локальной копии каждого хостинга, есть в плане проверок для образов Docker.
Авторы статьи: Андрей Олегович, Arthur Isaev