
🔄 WordPress в подкаталоге: как перенести установку из корня и обратно
Знакомая ситуация: вы подняли сайт разработки, сверстали тему, подключили контент через WP Migrate DB Pro, и после пуша обнаружили битые стили, потерянные картинки и неработающий wp-admin. Причина почти всегда одна: production стоит в корне, а dev, в подкаталоге (или наоборот), и прямой поиск-замена URL в базе эту разницу не лечит.
Проблема глубже, чем кажется: в корневой установке WordPress все ссылки (и на страницы, и на медиафайлы) используют один и тот же домен. В подкаталоговой установке ссылки на контент идут от адреса сайта, а ссылки на ресурсы (css, js, изображения), от адреса WordPress. Обычный search-replace по базе заменяет все одинаково и ломает половину путей.
В этом руководстве, два проверенных маршрута миграции (туда и обратно) с конкретными настройками поиска-замены, подготовкой wp-config.php и правильным порядком переноса файлов. После прочтения вы либо приведёте dev и production к единой схеме, либо осознанно проведёте миграцию между разнотипными установками без сломанного фронтенда.
💡 Быстрый обзор:
- Определите тип установки: совпадают ли «Адрес WordPress» и «Адрес сайта» в Настройках → Общие
- Для миграции из подкаталога в корень: захардкодьте WP_SITEURL в wp-config, выполните замену
/subdir→/в базе данных, перенесите файлы на уровень выше, обновите корневой index.php - Для миграции из корня в подкаталог: замените в БД только пути к
/wp-content, обновите адрес WordPress в настройках, создайте подкаталог, скопируйте index.php и.htaccess обратно в корень - После любой миграции зайдите в Настройки → Постоянные ссылки и нажмите «Сохранить»: это перестроит структуру URL и сбросит кеш
Как определить, где установлен WordPress
Если вы ставили WordPress вручную, вы наверняка помните, был ли это корень домена или подкаталог вроде /wp либо /blog. Но если сайт унаследован от предыдущего разработчика, развёрнут хостингом в один клик или прошло несколько лет, детали стираются.
Самый быстрый способ: зайдите в админку WordPress, откройте Настройки → Общие и посмотрите на поля «Адрес WordPress (URL)» и «Адрес сайта (URL)». Если значения совпадают, перед вами корневая установка:

Если поля различаются, WordPress установлен в подкаталог (в примере ниже это /subdir):

Дополнительный признак подкаталоговой установки: при входе в админку URL содержит подкаталог, например, example.com/wp/wp-admin/ вместо example.com/wp-admin/.
Почему нельзя просто взять и перенести
Корень проблемы, в двойной системе URL, которую WordPress использует при установке в подкаталог. Давайте разложим на конкретных примерах.
Допустим, у вас корневая установка на example.com. Абсолютно все ссылки в базе данных, и на пост /2025/about-page, и на картинку /wp-content/uploads/photo.jpg, начинаются с //example.com. Обычный поиск-замена //example.local → //example.com работает идеально.
Теперь возьмём установку в подкаталоге /wp. Ссылка на тот же пост выглядит как //example.com/about-page (через адрес сайта), а ссылка на ту же картинку, //example.com/wp/wp-content/uploads/photo.jpg (через адрес WordPress с подкаталогом). Простая замена //example.local → //example.com сломает медиафайлы: система будет искать их без /wp в пути и получит 404.
Таблица ниже показывает, какие группы URL нужно обновлять в каждом направлении миграции:
Направление | URL страниц и постов | URL медиафайлов и ресурсов | Пути к файлам в БД |
|---|---|---|---|
Подкаталог → корень | Заменить | Заменить | Заменить |
Корень → подкаталог | Оставить как есть | Заменить | Заменить |
Кроме базы данных, нужно физически переместить файлы и обновить index.php в корне, иначе WordPress не найдёт wp-blog-header.php. Дальше разберём оба маршрута по шагам.
Способ 1: перенос WordPress из подкаталога в корень
Это более простое направление: вы убираете подкаталог из путей, и все URL становятся «плоскими», как в стандартной установке.
Шаг 0: диагностика того, что пойдёт не так
Перед вмешательством полезно увидеть масштаб проблемы своими глазами. На скриншоте ниже, настройки миграции с wp-in-a-subdirectory.local (WordPress в /subdir) на wp-standard-install.local (корневая установка). Настройки WP Migrate DB Pro, стандартные, плюс замена заголовка сайта для демонстрации:

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

В HTML видны ссылки на ресурсы с мёртвым путём /subdir, которого на целевом сервере уже нет. Попытка зайти в wp-admin вызывает редирект на wp-standard-install.local/subdir/wp-login.php, а такого файла не существует. Теперь починим.
Шаг 1: подготовка
Первым делом защитим доступ к админке на время миграции. Добавьте в wp-config.php константы, которые переопределят настройки из базы данных, так WordPress продолжит пускать вас в админку по старому пути с подкаталогом, даже после того как мы почистим базу:
1 define( 'WP_SITEURL', 'http://wp-in-a-subdirectory.local/subdir' ); 2 define( 'WP_HOME', 'http://wp-in-a-subdirectory.local' );
Затем положите сайт на техобслуживание: отредактируйте index.php в публичном корне, закомментируйте строку require( dirname( __FILE__ )... и после закрывающего тега ?> вставьте html-заглушку с сообщением о кратком простое. Посетители увидят такое:

Вы при этом продолжаете заходить в админку по адресу http://wp-in-a-subdirectory.local/subdir/wp-admin/, константа WP_SITEURL работает.
Шаг 2: поиск и замена в базе данных
Теперь чистим базу. Запустите поиск-замену с такими парами (показано в интерфейсе WP Migrate DB Pro, но тот же принцип работает с WP-CLI search-replace или SQL-запросами через phpMyAdmin):
//wp-in-a-subdirectory.local/subdir→//wp-in-a-subdirectory.local/app/public/subdir→/app/public(путь к файлам на сервере)

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

Шаг 3: физический перенос файлов
Удалите (или закомментируйте) строки с WP_SITEURL и WP_HOME из wp-config.php. Теперь админка отвалится, и сразу переносите файлы из подкаталога на уровень выше.
Через SSH или командную строку на сервере это делается в три команды:
1 rm index.php && mv subdir/* . && rm -rf subdir
Через FTP или файловый менеджер хостинга, перетащите всё содержимое подкаталога в публичный корень с заменой index.php:

Готово. Сайт открывается по корневому URL, картинки и стили на месте:

Последний штрих: зайдите в админку (теперь это http://wp-in-a-subdirectory.local/wp-admin без подкаталога), откройте Настройки → Постоянные ссылки и нажмите «Сохранить изменения», даже если ничего не меняли. WordPress перестроит структуру URL и сбросит кеш.
Способ 2: перенос WordPress из корня в подкаталог
Многие разработчики считают установку WordPress в подкаталог хорошей практикой: файлы ядра не захламляют корень, упрощается управление через Git/Composer, а сам домен можно использовать для других приложений. Но миграция существующего сайта в подкаталог объективно сложнее обратной, потому что теперь часть ссылок ДОЛЖНА сохранить подкаталог, а часть, нет.
Шаг 0: диагностика
Та же исходная точка: пробуем стандартную миграцию с корневой установки wp-standard-install.local на подкаталоговую wp-in-a-subdirectory.local (WordPress в /subdir):

Результат полностью ожидаем: страницы открываются, но стили и изображения бьются, к ним не дописан /subdir в пути:

В отличие от первого сценария, ссылки на посты и страницы работают корректно, они и не должны содержать подкаталог. Ломаются именно ресурсы (css, js, медиа), пути к которым теперь обязаны включать /subdir.
Шаг 1: подготовка
Константы WP_SITEURL и WP_HOME на этом этапе НЕ прописываем, наша поиск-замена не коснётся этих значений, и мы обновим адрес WordPress вручную чуть позже.
Страницу обслуживания ставим тем же способом: комментируем require(...) в index.php и добавляем html-заглушку. Посетители видят сообщение о техобслуживании, а вы продолжаете заходить в админку по http://wp-standard-install.local/wp-admin/.
Шаг 2: выборочный поиск-замена в базе
Ключевое отличие от первого способа: мы заменяем ТОЛЬКО пути к файлам и ресурсам, НЕ трогая URL страниц. Для этого таргетируем замену по маске /wp-content:
//wp-standard-install.local/wp-content→//wp-standard-install.local/subdir/wp-content/app/public→/app/public/subdir(путь на сервере)

После миграции проверяем содержимое постов: ссылки на другие страницы сайта НЕ содержат подкаталог (правильно), а встроенные изображения, содержат (тоже правильно):

Шаг 3: обновление адреса WordPress и перенос файлов
Теперь зайдите в Настройки → Общие и допишите подкаталог в конец «Адрес WordPress (URL)», например, http://wp-standard-install.local/subdir. Сразу после сохранения админка отвалится, потому что WordPress попытается найти файлы по новому пути, а их там ещё нет:

Создайте подкаталог subdir в публичном корне и переместите в него ВСЕ файлы WordPress. Затем скопируйте index.php и .htaccess ОБРАТНО в корень, чтобы страница обслуживания продолжала висеть, пока мы заканчиваем:

Восстановите index.php ВНУТРИ подкаталога до заводского состояния, уберите html-заглушку и раскомментируйте строку с require:
1 <?php 2 define( 'WP_USE_THEMES', true ); 3 require( dirname( __FILE__ ) . '/wp-blog-header.php' );
Теперь админка снова доступна по адресу http://wp-standard-install.local/subdir/wp-admin/:

Проверьте контент, картинки на месте, стили подгружаются:

Финальный штрих: корневой index.php
Осталось обновить index.php в публичном корне. Уберите страницу обслуживания и пропишите актуальный путь к wp-blog-header.php с учётом подкаталога:
1 <?php 2 define( 'WP_USE_THEMES', true ); 3 require( dirname( __FILE__ ) . '/subdir/wp-blog-header.php' );
Сайт открывается по корневому URL, все ресурсы собраны из подкаталога:

Снова зайдите в Настройки → Постоянные ссылки и сохраните без изменений, чтобы WordPress освежил структуру URL.
Альтернативные инструменты и официальный метод
Описанный выше подход с WP Migrate DB Pro удобен, но не единственен. Вот с чем ещё можно работать:
WP-CLI
search-replace. Командаwp search-replace '//oldsite.local/subdir' '//newsite.com'с флагом--dry-runсначала покажет, сколько вхождений будет заменено. Для выборочной замены (корень → подкаталог) сузьте маску:wp search-replace '//newsite.com/wp-content' '//newsite.com/subdir/wp-content'.Официальный метод WordPress. Документация developer.wordpress.org описывает процедуру «Giving WordPress Its Own Directory», с детальными конфигурациями для Apache (.htaccess), nginx (server block) и IIS (web.config). Метод не требует плагинов и работает на любом хостинге.
Ручной SQL. Если объём правок невелик, можно выполнить
UPDATE wp_posts SET post_content = REPLACE(post_content, '/subdir/', '/')напрямую в phpMyAdmin, но обязательно с предварительным бэкапом, сериализованные данные в wp_options и wp_postmeta такая замена разрушит.
Какой бы инструмент вы ни выбрали, правило одно: при миграции КОРЕНЬ → ПОДКАТАЛОГ заменяйте только /wp-content и файловые пути; при миграции ПОДКАТАЛОГ → КОРЕНЬ заменяйте всё, что ссылается на подкаталог.
⁉️🤔 Частые вопросы
Обязательно ли использовать WP Migrate DB Pro для такой миграции?
Нет. WP Migrate DB Pro просто даёт удобный интерфейс для поиска-замены с пониманием сериализованных данных PHP. Технически вы можете выполнить те же замены через WP-CLI (команда
wp search-replaceтоже корректно обрабатывает serialized strings) или использовать официальный метод WordPress с ручным переносом файлов и правкой index.php. Плагин экономит время на крупных и средних проектах, где много вхождений.
Что делать, если после миграции часть картинок всё равно не грузится?
Самая частая причина: в базе остались жёстко прописанные URL с абсолютными путями старого сервера, которые не попали под маску замены. Проверьте контент постов через phpMyAdmin:
SELECT ID, post_content FROM wp_posts WHERE post_content LIKE '%/subdir/%'(или старый домен). Второй кандидат, кеш браузера и CDN: сбросьте и проверьте в режиме инкогнито.
Нужно ли обновлять.htaccess после переноса?
Если вы используете красивые постоянные ссылки, да, но WordPress делает это сам, когда вы нажимаете «Сохранить» на странице Настройки → Постоянные ссылки. Если прав на запись у сервера нет, WordPress покажет готовое содержимое.htaccess, скопируйте его вручную. При миграции в подкаталог убедитесь, что корневой.htaccess (не тот, что внутри подкаталога) не содержит правил, конфликтующих с новой структурой.
Можно ли выполнить миграцию вообще без даунтайма?
Технически да, если использовать метод с.htaccess-редиректами (Method I из официальной документации WordPress, «Без смены URL»). При таком подходе файлы переносятся в подкаталог, а корневой.htaccess бесшовно направляет все запросы в новое расположение. Посетители не замечают переезда. Минус: вы остаётесь на том же домене, и URL сайта формально не меняется (подкаталог не виден в адресной строке).
Почему WP Migrate DB Pro не поддерживает миграцию между разнотипными установками из коробки?
Разработчики Delicious Brains обсуждали это на GitHub почти три года. Корень проблемы: плагин применяет ОДНУ пару поиск-замена ко ВСЕЙ базе, а для миграции между root и subdirectory нужны РАЗНЫЕ замены для разных групп URL (страницы vs ресурсы). Автоматическое определение того, какой именно URL к какой группе относится, потребовало бы парсинга структуры контента, что выходит за рамки простого search-replace. Поэтому текущая рекомендация: приведите сайты к единой схеме установки ДО миграции.

Что в итоге: корень или подкаталог?
Выбор между корневой и подкаталоговой установкой WordPress, по сути, сводится к одному компромиссу. Корневая установка проще: меньше движущихся частей, прямая совместимость между dev и production, никаких сюрпризов с двойными URL. Подкаталоговая, чище архитектурно: файлы ядра изолированы, в корне лежит только index.php, легче обновлять WordPress через Git/Composer и безопаснее держать несколько приложений на одном домене.
Если у вас один production-сайт и один dev-сайт, приведите оба к единой схеме (любой) и забудьте о проблеме. Если вы работаете в команде, где часть проектов исторически в корне, а часть, в подкаталоге, теперь вы знаете точные маски поиска-замены для каждого направления.
Главное правило, которое стоит положить в закладки: при миграции КОРЕНЬ → ПОДКАТАЛОГ трогайте только /wp-content и файловые пути; при миграции ПОДКАТАЛОГ → КОРЕНЬ, заменяйте всё, что содержит подкаталог. И всегда, всегда нажимайте «Сохранить» в Постоянных ссылках после переезда.



