Короткий ответ
404 после переноса почти никогда не «пропали файлы целиком». Обычно новый nginx/Apache смотрит не в /var/www/site.example, try_files не отдаёт фронтконтроллер, .htaccess не читается без AllowOverride, либо в БД остался старый siteurl. IP нового сервера — 10.0.20.40. Проверяйте через /etc/hosts или --resolve, не через публичный DNS: он ещё может бить в старый узел — DNS.
Не удаляйте старый webroot, пока новый стенд не отдаёт те же URL. План переноса: безопасный cutover.
Симптомы и как отличить
/открывается,/about/— 404 nginx (короткая страница, не тема WordPress).- Все URL 404, даже
index.php. - HTML есть, но CSS/картинки 404 из‑за абсолютных
http://old.example. - 403 вместо 404 — права, не rewrite: 403.
| Что | Куда смотреть |
|---|---|
404 с Server: nginx и телом nginx | root/try_files |
| 404 тема WordPress «страница не найдена» | permalinks/БД, приложение живо |
| 500 | PHP |
| Открывается другой сайт | default_server / Host |
Возможные причины
root/DocumentRootуказывает на/var/www/htmlдефолта, архив лежит в/var/www/site.example.- Файлы залиты в подкаталог
public//web/, а vhost смотрит на уровень выше (или наоборот). - Nginx без
try_files $uri $uri/ /index.php?$args— ЧПУ отдают 404. - Apache:
AllowOverride None,RewriteEngineв.htaccessмёртв. - WordPress
permalink_structure, нетmod_rewrite/ нет правил nginx. siteurl/homeвwp_optionsсо старым доменом илиhttp://.- Регистр: с Windows
Index.PHP, на Ubuntu регистр важен. - Симлинки, не попавшие в rsync без
-l. - Только часть файлов: забыли
wp-content/uploads.
Диагностика
1. Куда реально ходит запрос
curl -sI --resolve site.example:80:10.0.20.40 http://site.example/ | head
curl -sI --resolve site.example:80:10.0.20.40 http://site.example/index.php | head
curl -sI --resolve site.example:80:10.0.20.40 http://site.example/not-a-file | head
sudo nginx -T 2>/dev/null | grep -A20 'server_name site.example'
sudo apache2ctl -S 2>/dev/nullСравните root с ls /var/www/site.example.
2. Есть ли фронтконтроллер
ls -la /var/www/site.example/index.php /var/www/site.example/public/index.php /var/www/site.example/web/index.php 2>/dev/null
find /var/www/site.example -maxdepth 3 -name index.phpLaravel/Symfony часто требуют root .../public.
3. Rewrite
Nginx:
sudo grep -n try_files /etc/nginx/sites-enabled/*Apache:
sudo apache2ctl -M | grep rewrite
sudo grep -n AllowOverride /etc/apache2/sites-enabled/*
sudo tail -n 20 /var/www/site.example/.htaccess4. URL в приложении
WordPress (WP-CLI, если стоит):
sudo -u www-data wp --path=/var/www/site.example option get siteurl
sudo -u www-data wp --path=/var/www/site.example option get home
sudo -u www-data wp --path=/var/www/site.example option get permalink_structureБез WP-CLI — SQL к таблице опций, не правьте сериализованные строки руками вслепую.
5. Регистр и пропущенные файлы
find /var/www/site.example -name '*.PHP' -o -name '*.HTM' | head
du -sh /var/www/site.exampleСравните размер с исходным сервером.
Решение
sudo tar -C /var/www -czf /root/site.example-before-fix-$(date +%F).tar.gz site.exampleСценарий A. Неверный root
server {
listen 80;
server_name site.example www.site.example;
root /var/www/site.example;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}Для приложения с public/:
root /var/www/site.example/public;nginx -t и reload. Apache: DocumentRoot /var/www/site.example/public плюс <Directory> с AllowOverride All или явные RewriteRule в vhost (предпочтительнее, чем слепой All).
Сценарий B. ЧПУ WordPress на nginx
Нужен try_files на index.php. permalinks в админке «Простые» работают без rewrite, «Название записи» — нет. После фикса откройте «Настройки → Постоянные ссылки» и сохраните (пересоберёт правила Apache).
Сценарий C. siteurl в БД
Через WP-CLI:
sudo -u www-data wp --path=/var/www/site.example search-replace 'http://old.internal' 'https://site.example' --all-tablesДелайте на копии БД. Для сериализованных данных — только WP-CLI/wp search-replace, не голый sed по дампу.
Сценарий D. Регистр
Приведите имена к фактическим URL. Не включайте mod_speling как постоянное решение.
Сценарий E. Забыли uploads
Догоните rsync:
rsync -aH --numeric-ids src:/var/www/site.example/wp-content/uploads/ /var/www/site.example/wp-content/uploads/Права www-data, не 777.
Сценарий F. Мультисайт и COOKIE_DOMAIN
После переноса поддомены shop.site.example 404, apex жив: в wp-config.php DOMAIN_CURRENT_SITE / PATH_CURRENT_SITE остались со старого стенда, nginx server_name без wildcard. Добавьте server_name site.example *.site.example только если сертификат SAN это покрывает, иначе отдельный vhost. DNS A для поддомена тоже должен смотреть на 10.0.20.40.
Регистр файлов: с Windows-архива Assets/Logo.PNG, URL в CSS /assets/logo.png — на ext4 404. Найдите find /var/www/site.example -iname logo.png. Не включайте mod_speling в проде.
Как проверить, что проблема устранена
Список URL из старого access.log / карты сайта:
for u in / /index.php /about/ /wp-login.php; do
echo -n "$u "
curl -s -o /dev/null -w '%{http_code}\n' --resolve site.example:443:10.0.20.40 "https://site.example$u"
doneОжидание: 200/302 на живые страницы, 404 только на заведомо несуществующие. Главная не должна быть «чужим» default vhost.
Проверьте CSS: не mixed content из старых http-ссылок.
Если не помогло
- 404 только с телефона: кэш CDN, Purge, Host.
index.php200, ЧПУ 404 на Apache:AllowOverrideиrewrite.- Мультисайт WordPress:
DOMAIN_CURRENT_SITEвwp-config.php. - Старый сервер всё ещё в DNS — вы тестируете не тот узел.
Профилактика
- Чеклист миграции: root, PHP socket, rewrite, search-replace, hosts-проверка, затем DNS.
- Хранить эталон vhost в git.
- Не полагаться на
.htaccessкак на единственный rewrite на nginx (он его не читает).
FAQ
Почему /index.php/about работает, а /about нет?
Rewrite не доводит ЧПУ до фронтконтроллера. Это классика после переноса на nginx.
Нужно ли копировать /var/www/html?
Нет, если сайт жил в /var/www/site.example. Дефолтный html Ubuntu — ловушка.
404 на .php, html открывается
PHP location не совпал (\.php$ vs try_files отдал файл как статику 404, если файла нет). Сверьте fastcgi_split_path_info.
Можно ли Alias /blog → другой каталог?
Да, но try_files и SCRIPT_FILENAME должны знать этот Alias. Иначе 404/500.
После смены домена 404 на превью Elementor/WP
Кэш плагина и старые URL в CSS. Сброс кэша после search-replace, не rm -rf wp-content.