Как отладить 404 в REST API WordPress: маршруты, permalinks и конфликтующие плагины

Если запрос к /wp-json/ работает, а конкретный эндпоинт отвечает 404 Not Found, проблема обычно не в «поломанном REST API вообще», а в одном из трёх мест: маршрут не зарегистрирован, его перехватывает другой код, либо сервер/постоянные ссылки отдают не тот набор правил. Ниже — рабочая схема диагностики, которая помогает не гадать, а быстро сузить причину.

Когда 404 в REST API — это действительно проблема маршрута

Сначала важно отделить ошибку маршрута от ошибки авторизации и от обычного 404 страницы. В REST API WordPress есть разница между ответом rest_no_route и, например, rest_forbidden. Если маршрут не найден, вы увидите именно 404 в JSON-ответе, а не HTML-страницу темы.

Что проверить первым делом

  • Открывается ли /wp-json/ и возвращает ли список namespace.
  • Есть ли 404 только у одного маршрута или у всех маршрутов конкретного плагина.
  • Не менялся ли недавно permalink-формат в админке.
  • Не добавлялся ли код в functions.php, который регистрирует REST-маршруты слишком поздно.
  • Не стоит ли на сайте плагин безопасности, который фильтрует REST-запросы.

Если /wp-json/ открывается, а отдельный маршрут нет, значит ядро REST живо, и искать надо в регистрации эндпоинта или в конфликте с плагином/темой.

Диагностика: как понять, где именно ломается запрос

Самый полезный шаг — посмотреть, есть ли маршрут в реестре WordPress. Для этого можно временно повесить отладочный код в mu-plugin или в тестовый плагин. Не оставляйте его в продакшене надолго, но для проверки он удобен.

<?php
/**
 * Plugin Name: REST Route Debug
 */
add_action( 'rest_api_init', function () {
    $routes = rest_get_server()->get_routes();

    error_log( 'REST routes count: ' . count( $routes ) );

    if ( isset( $routes['/myplugin/v1/items'] ) ) {
        error_log( 'Route /myplugin/v1/items is registered' );
    } else {
        error_log( 'Route /myplugin/v1/items is missing' );
    }
} );

Если нужного маршрута нет, значит код регистрации не отработал или отработал не в тот момент. Если маршрут есть, но запрос всё равно даёт 404, тогда уже смотрим callback, namespace, аргументы и фильтры, которые могут менять ответ.

Проверка через WP-CLI

Если есть доступ к WP-CLI, удобно быстро проверить REST без браузера. Команда wp eval позволяет вызвать маршрут и увидеть ответ в консоли.

wp eval '
$response = rest_do_request( new WP_REST_Request( "GET", "/myplugin/v1/items" ) );
echo $response->get_status() . PHP_EOL;
echo wp_json_encode( $response->get_data(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ) . PHP_EOL;
'

Если статус 404, а маршрут должен существовать, это уже не проблема фронтенда. Если статус 200 в CLI и 404 в браузере, чаще всего мешает прокси, кеш или серверное правило.

Пошаговое решение: от регистрации маршрута до проверки permalink

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

1. Убедиться, что маршрут регистрируется на rest_api_init

Маршруты REST нужно регистрировать на хук rest_api_init. Если код выполняется раньше, WordPress просто не увидит endpoint в нужный момент.

<?php
add_action( 'rest_api_init', function () {
    register_rest_route( 'myplugin/v1', '/items', array(
        'methods'  => WP_REST_Server::READABLE,
        'callback' => 'myplugin_get_items',
        'permission_callback' => '__return_true',
    ) );
} );

function myplugin_get_items( WP_REST_Request $request ) {
    return rest_ensure_response( array(
        'items' => array(),
    ) );
}

Обратите внимание на permission_callback. В новых версиях WordPress его отсутствие — плохая практика, а в некоторых конфигурациях это ещё и источник предупреждений. Для публичного маршрута можно использовать __return_true, но для приватного лучше писать явную проверку прав.

2. Проверить, не конфликтует ли namespace

Если два плагина регистрируют одинаковый namespace и путь, один может перезаписать другой. Это особенно заметно в проектах, где несколько модулей используют одинаковые префиксы вроде api/v1 или custom/v1. Namespace должен быть уникальным и предсказуемым.

Практика простая: используйте префикс, связанный с проектом или плагином, а не общий шаблон. Например, companyname/v1 лучше, чем просто v1.

3. Сбросить правила постоянных ссылок

Если REST-маршрут завязан на rewrite-правила, после миграции, смены домена или ручного редактирования .htaccess может понадобиться сбросить permalink-правила. Самый безопасный способ — открыть Настройки → Постоянные ссылки и нажать «Сохранить» без изменения настроек.

Если нужен программный вариант после активации плагина, можно сделать flush один раз:

<?php
register_activation_hook( __FILE__, function () {
    flush_rewrite_rules();
} );

register_deactivation_hook( __FILE__, function () {
    flush_rewrite_rules();
} );

Важно: не вызывайте flush_rewrite_rules() на каждом запросе. Это тяжёлая операция, и в продакшене она быстро превращается в лишнюю нагрузку.

4. Исключить фильтры безопасности и кеш

Некоторые security-плагины ограничивают REST API, особенно если запрос идёт без авторизации или с нестандартными заголовками. Кеш-плагины тоже могут отдавать старый ответ, если маршрут был недавно добавлен.

Что делать:

  • временно отключить плагин безопасности и повторить запрос;
  • очистить кеш страницы и объектный кеш, если он есть;
  • проверить, не кешируется ли ответ /wp-json/ на уровне CDN;
  • посмотреть, не меняет ли сервер заголовок Accept или путь запроса.

Сравнение подходов: плагин, код или серверная правка

ПодходКогда подходитМинус
Проверка через кодНужно понять, зарегистрирован ли маршрутТребует доступа к файлам и логам
Сброс permalink в админкеПосле миграции или смены структуры ссылокНе помогает, если маршрут не зарегистрирован
Отключение конфликтующего плагинаПодозрение на security/cache/REST-фильтрНужно тестировать по одному модулю

Проверка результата после внедрения

После правок не ограничивайтесь открытием страницы в браузере. Проверьте маршрут тремя способами: через браузер, через WP-CLI и через лог сервера, если он доступен. Это помогает поймать ситуацию, когда фронтенд уже показывает 200, а старый кеш ещё отдаёт 404.

  • Откройте конкретный endpoint в браузере и убедитесь, что ответ JSON, а не HTML-страница темы.
  • Повторите запрос через wp eval или curl.
  • Очистите кеш плагина, серверный кеш и CDN, если они используются.
  • Проверьте, что маршрут доступен не только для администратора, но и для нужной роли или для гостя — в зависимости от логики permission_callback.

Если маршрут публичный, ответ должен быть одинаковым для анонимного пользователя. Если он приватный, ожидаемым результатом может быть не 404, а 401 или 403 — это уже нормальная защита, а не ошибка маршрута.

Частые ошибки и как их исправить

Маршрут зарегистрирован в init, а не в rest_api_init

Такой код иногда «случайно» работает на одном сайте и ломается на другом. WordPress не гарантирует, что REST-сервер уже готов в init. Перенесите регистрацию в rest_api_init.

Неверный namespace или путь

Ошибка в одном символе даёт тот же 404, что и отсутствие маршрута. Проверьте, совпадает ли путь в register_rest_route() с тем, что вы запрашиваете в браузере. Особенно часто путают слэши в начале и конце.

Нет permission_callback или он возвращает false

Если callback закрывает доступ, WordPress может вернуть не тот статус, который вы ожидаете. Для отладки временно поставьте явную проверку и посмотрите, что именно возвращается.

Кеширует CDN или reverse proxy

После изменения маршрута старый ответ может жить в кеше. Очистка только в WordPress тут не помогает. Нужна очистка на уровне прокси или CDN.

Плагин безопасности режет REST

Некоторые плагины отключают REST для гостей или для определённых namespace. Если после деактивации проблема исчезает, ищите настройку ограничения REST, а не «чините» маршрут дальше.

Мини-чек-лист перед тем как считать проблему решённой

  • Маршрут виден в rest_get_server()->get_routes().
  • Запрос к endpoint возвращает JSON, а не HTML 404.
  • Проверка через WP-CLI даёт тот же статус, что и браузер.
  • Кеш плагина, сервера и CDN очищен.
  • permission_callback настроен осознанно, а не оставлен случайно.
  • Namespace уникален и не пересекается с другим плагином.

Практические советы по безопасности и производительности

Если маршрут публичный, не возвращайте лишние данные «на всякий случай». REST-ответы часто индексируются, кешируются и логируются. Отдавайте только то, что реально нужно клиенту. Для приватных маршрутов всегда проверяйте права через current_user_can() или через более узкую бизнес-логику, а не только по факту авторизации.

Для производительности не делайте тяжёлые запросы в callback без необходимости. Если endpoint часто вызывается, подумайте о кешировании результата через transients или объектный кеш, но только если данные не должны быть мгновенно актуальными. И обязательно проверяйте, не ломает ли кеш обновление ответа после изменения контента.

Если нужен быстрый аудит сайта на дубли, лишние скрипты и технические хвосты, иногда удобнее сначала убрать шум в конфигурации, а уже потом разбирать REST-ошибки. В таких задачах может помочь Clearfy Pro, но только как инструмент для чистки и упрощения, а не как замена отладке маршрутов.

⭐⭐⭐⭐⭐