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

Сеть самостоятельных сайтов на одной установке движка

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

Сценарии у сети типовые: городские или региональные порталы под общим брендом, издание с самостоятельными разделами, студия с витринами клиентов, учебный проект, где каждый слушатель получает собственный сайт. Сотня сайтов на одной установке - обычное техническое дело, ограничение упирается скорее в содержание и дисциплину обслуживания, чем в движок. Актуальная версия WordPress 7.1.3 поддерживает мультисайт из коробки, ничего докупать не нужно.

У медали есть оборот. Все сайты делят одну кодовую базу, один сервер и одну базу: ошибка в общем коде или неудачное обновление валит всю сеть разом. Включение мультисайта превращает повторяющуюся рутину одиночных установок в единую процедуру с большой зоной последствий, и первое решение в ней - адресация.

Включение мультисайта через константу WP_ALLOW_MULTISITE

Сеть включается одной строкой в wp-config.php. Строка добавляется к остальным константам, выше комментария о конце области правок:

define('WP_ALLOW_MULTISITE', true);

После сохранения файла в консоли администратора появляется новый пункт: раздел "Инструменты" и команда "Установить сеть". Мастер перед включением попросит выключить плагины и включить постоянные ссылки. Это не формальность: от конфликтов плагинов запуск иногда идёт наперекосяк, а без постоянных ссылок адресация новых сайтов работает криво. Дальше мастер спросит название сети и адрес суперадминистратора, а затем покажет блок констант для wp-config.php и правила для сервера. Блок выглядит так:

define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);
define('DOMAIN_CURRENT_SITE', 'example.com');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);

Здесь и спрятано главное решение. SUBDOMAIN_INSTALL со значением true строит сеть на поддоменах, false оставляет сайты в подкаталогах. Мастер подставляет значение сам, исходя из выбранного режима, но понимать строку стоит до нажатия кнопок. После сохранения файла и повторного входа в панели появляется меню со списком сайтов и пунктом "Управление сетью", а сетевая админка живёт по адресу /wp-admin/network/. Суперадминистратор видит все сайты сети, администратор отдельного сайта - только свой.

Выбор между поддоменами и подкаталогами до запуска сети

Режим адресации выбирается один раз и на всю жизнь сети. Решение упирается в три вопроса:

  1. сайты связаны одной темой и брендом или живут самостоятельной жизнью;
  2. есть ли доступ к DNS для wildcard записи и возможность подтвердить домен по TXT записи;
  3. планируется ли у отдельных сайтов собственный домен в будущем.

Подкаталоги означают адреса вида основной домен плюс путь до раздела. Плюсы весомые: DNS трогать не надо, сертификат остаётся один, конфигурация nginx почти не меняется, разделы наследуют накопленную историю домена и выглядят частями одного сайта. Минусы тоже честные. Общее пространство путей требует проверять имена: новый сайт shop конфликтует со страницей или рубрикой с тем же адресом. Записи главного сайта получают префикс blog в пути. И главное ограничение: режим подкаталогов мастер предлагает только установкам, живущим меньше месяца, из-за конфликтов со старыми постоянными ссылками. Сайту с историей WordPress оставит один вариант, поддомены. Месячный лимит существует из-за старых постоянных ссылок: чем дольше живёт сайт, тем больше адресов уже занято, и тем выше риск конфликтов при включении сети.

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

Что будет, если передумать. Константу SUBDOMAIN_INSTALL в файле поменять можно за секунду, но адреса страниц всех сайтов хранятся в базе в старом формате, и после смены режима ссылки посыплются. Штатного инструмента миграции нет, ручная правка тысяч адресов в базе сравнима с переездом на новый домен. Поэтому режим выбирают первым, а не последним.

Поддомены с wildcard записью DNS и выпуском сертификата

Для режима поддоменов DNS получает запись A с именем из звёздочки, указывающую на IP сервера. После этого любой новый сайт сети получает рабочий адрес без визита в панель регистратора. Работоспособность wildcard проверяется запросом любого выдуманного поддомена: если ответ совпадает с адресом сервера, запись живая.

dig +short news.example.com

Nginx принимает основной домен и все поддомены одним блоком:

server {
    listen 443 ssl;
    server_name example.com *.example.com;
    root /var/www/example.com/public;
    index index.php index.html;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }
}

С сертификатом сложнее. Обычная проверка по HTTP для wildcard не работает: центр требует доказать владение доменом TXT записью вида _acme-challenge. certbot запускается в ручном режиме:

certbot certonly --manual --preferred-challenges dns -d example.com -d '*.example.com'

Утилита печатает текст для TXT записи, админ добавляет его в панели DNS, после распространения записи выпуск продолжается. Подпись живёт 90 дней, и у ручного режима есть неудобство: продление снова требует TXT записи, автопилота нет. Для автоматизации ставится DNS модуль certbot под конкретного провайдера зон: TXT записи появляются и удаляются сами, продление идёт без участия человека. Альтернатива без wildcard выпускает сертификат списком поддоменов и расширяет список по мере роста сети:

certbot --nginx --expand -d example.com -d news.example.com -d shop.example.com

Такой сертификат живёт на обычной HTTP проверке и продлевается сам, минус очевиден: каждый новый сайт сети требует перевыпуска сертификата. При десятках адресов wildcard удобнее, при двух-трёх поддоменах расширение списка выглядит даже проще.

Подкаталоги и правила rewrite в конфигурации nginx

Режим подкаталогов не требует ни DNS, ни отдельного сертификата: весь трафик идёт на основной домен. Зато nginx должен объясняться с адресами вида /news/wp-admin, которых физически не существует. Официальные правила rewrite для сетей, включённых на WordPress 3.5 и новее, выглядят скромно:

location / {
    try_files $uri $uri/ /index.php?$args;
    rewrite /wp-admin$ $scheme://$host$request_uri/ permanent;
    rewrite ^(/[^/]+)?(/wp-.*) $2 last;
    rewrite ^(/[^/]+)?(/.*\.php) $2 last;
}

Читаются строки так. Первая достраивает слэш к адресам админок, прилетевшим без него. Вторая срезает префикс сайта с путей к wp-content, wp-admin и wp-includes: файлы этих каталогов лежат в корне установки, а не в каталогах сайтов. Третья делает то же для php-файлов. WordPress дальше сам определяет по списку путей в базе, какому сайту предназначен запрос. А try_files в начале отправляет всё несуществующее в index.php, как на одиночном сайте.

Сайты создаются из сетевой админки, пункт "Сайты" и команда "Добавить новый", или консольной утилитой:

wp site create --slug=news --title='Новости' --email=Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.'
wp site list

Значение slug становится путём сайта: домен плюс news. Тему для нового сайта сначала разрешают в сетевой панели раздела тем, плагины включаются либо сети целиком, либо точечно. Забыть сетевое разрешение и гадать, почему у администратора сайта не видна нужная тема, - обычная история первых недель с сетью.

Общая база данных и файлы каталога sites после запуска сети

После включения сети база перестраивается по принципу общих ресурсов и номерных частей. Общими остаются таблицы списка сайтов, метаданные сети и учётные записи пользователей. Каждый сайт получает собственный набор таблиц с номером в префиксе: у второго сайта это wp_2_posts, wp_2_options и около десятка соседей, у третьего номера тройка. Номер совпадает с идентификатором сайта в сетевом списке:

wp site list
wp db query "SHOW TABLES LIKE 'wp_2_%'"

Сетевая панель выводит идентификатор в таблице сайтов, тот же номер показывает утилита wp site list, искать его руками не придётся. Загруженные файлы размечены так же: содержимое второго сайта лежит в wp-content/uploads/sites/2, третьего в sites/3. Физическая структура помогает бэкапам и переездам: один сайт сети вынимается со своим каталогом и своим набором таблиц. Пользователь у сети один на всех, роль назначается на каждом сайте отдельно, суперадминистратор проходит всюду. Отдельному сайту при желании назначается и полностью сторонний домен: сетевая админка позволяет прописать его в настройках сайта, документация относит приём к продвинутым.

Когда мультисайт не нужен и какие ограничения он приносит

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

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

По сути, вся настройка мультисайта сводится к трём вещам: константа в wp-config.php, мастер в админке и конфигурация сервера под выбранный режим. Из всего набора решений только одно не имеет кнопки отмены, и оно принимается первым. Поддомены под отдельные проекты и будущий переезд, подкаталоги под разделы одного бренда и свежие установки. Техническая часть после выбора укладывается в час. Всё остальное - темы, плагины, пользователи - правится в любой вечер и без последствий, а выигрыш в масштабе достаётся сразу всем проектам сети.