Гайд

Как понять, почему WordPress отвечает несколько секунд

Диагностика высокого TTFB от сети и статики до PHP, WordPress, SQL и page cache.

Автор: Сергей Кауров Опубликовано: не указано Обновлено: не указано Тема: WordPress performance Около 30 минут

Послойная диагностика высокого TTFB Последовательная проверка сети, статики, PHP, WordPress, SQL и кеша. Диагностика от сети до приложения Один и тот же запрос сравнивается на каждом слое Сеть DNS · TCP · TLS Статика Прямая отдача PHP Без WordPress WordPress Bootstrap SQL Доля базы Page cache Повторный запрос Меняйте один фактор и повторяйте тот же тест baseline → изменение → повторное измерение → вывод
1Зафиксировать baseline
2Разделить слои
3Изменить один фактор
4Повторить измерение

Для кого этот материал

Гайд полезен, если:

  • сайт формально доступен, но открывается медленно;
  • мониторинг показывает высокий response time или TTFB;
  • хостинг сообщает, что сервер работает штатно;
  • кеширующий плагин включён, но повторные запросы не ускоряются;
  • нужно локализовать проблему без перебора всех настроек WordPress.

Понадобятся:

  • curl на Linux, macOS или WSL;
  • доступ к файлам сайта;
  • возможность временно загрузить несколько PHP-файлов в корень WordPress рядом с wp-load.php.

Диагностические файлы нельзя оставлять на production после проверки. Ниже используется защитный токен, но он не превращает временный endpoint в постоянный инструмент мониторинга.

Один показатель времени не объясняет причину

Общее время HTTP-запроса состоит из нескольких этапов:

DNS
→ установка TCP-соединения
→ TLS-рукопожатие
→ ожидание первого байта
→ получение ответа

Высокое итоговое значение может означать разные проблемы:

Где растёт время Что проверять
DNS резолвер, DNS-записи, DNSSEC, authoritative DNS
TCP connect маршрут, firewall, перегрузку узла, потери пакетов
TLS сертификат, SNI, TLS termination, конфигурацию протоколов
Ожидание первого байта веб-сервер, очередь PHP, WordPress, плагины, тему, базу, внешние API
Получение тела размер ответа, сжатие, пропускную способность, медленную передачу

Фраза «WordPress отвечает две секунды» ещё не является диагнозом. Сначала нужно доказать, что задержка появляется при обработке приложения, а не раньше.

Готовый набор диагностических файлов

К статье приложен архив:

Архив диагностических файлов будет добавлен отдельно

Внутри:

Файл Что проверяет
diag-static.txt прямую отдачу статического файла
diag-php.php чистый PHP без WordPress
diag-wordpress-bootstrap.php загрузку WordPress без рендеринга шаблона
diag-wordpress-db.php время bootstrap, число и суммарное время SQL-запросов
diag-config.php общий токен доступа и безопасный JSON-ответ
measure-url.sh полную серию измерений одной командой

Скрипты не возвращают SQL-тексты, пути к конфигурации, пароли или содержимое базы. Файл базы данных также не создаётся. diag-wordpress-db.php включает SAVEQUERIES только на время одного запроса и выводит агрегаты.

1. Создайте защитный токен

На локальном компьютере:

openssl rand -hex 24

Команда вернёт строку наподобие:

b33ec12c25e34129a39ae4cb060863f6eb6c7acb1b86fe54

Откройте diag-config.php и замените:

define('DIAG_TOKEN', 'CHANGE_ME_TO_A_LONG_RANDOM_TOKEN');

на:

define('DIAG_TOKEN', 'b33ec12c25e34129a39ae4cb060863f6eb6c7acb1b86fe54');

Пока значение не изменено, диагностические скрипты возвращают 503 и не выполняют измерение.

Токен передаётся в HTTP-заголовке, а не в query string. Так он не попадает в обычные access-логи как часть URL.

2. Загрузите файлы в корень WordPress

Рядом с wp-load.php должны оказаться:

diag-config.php
diag-static.txt
diag-php.php
diag-wordpress-bootstrap.php
diag-wordpress-db.php
wp-load.php

measure-url.sh остаётся на локальном компьютере. На сервер сайта его загружать не нужно.

Если WordPress установлен в подпапке, файлы нужно разместить именно в каталоге, где находится wp-load.php, а в командах использовать адрес этой подпапки.

3. Запустите полный тест одной командой

chmod +x measure-url.sh
./measure-url.sh https://example.com YOUR_DIAG_TOKEN 5

Для WordPress в подпапке:

./measure-url.sh https://example.com/blog YOUR_DIAG_TOKEN 5

Последнее число — количество повторов каждого запроса.

Скрипт последовательно измеряет:

  1. публичную WordPress-страницу;
  2. статический файл;
  3. чистый PHP;
  4. bootstrap WordPress;
  5. WordPress с профилем SQL.

Он не сохраняет cookies и не следует редиректам. Поэтому нужно указывать канонический HTTPS-адрес, включая правильную подпапку и завершающий /.

Пример строки результата:

page#1   http=200 dns=2.31ms tcp=18.40ms tls=31.22ms wait=1468.73ms transfer=4.11ms total=1524.77ms

Здесь wait — интервал от окончания TLS до первого байта. Это ещё не «чистое время PHP»: в него входят передача запроса, очередь веб-сервера и сетевой путь первого байта обратно. Но именно этот показатель помогает отделить сетевое соединение от серверной обработки.

Шаг 1. Зафиксируйте исходный TTFB страницы

Все дальнейшие измерения нужно сравнивать с одним и тем же исходным URL.

Задайте переменные:

BASE_URL='https://example.com'
TOKEN='YOUR_DIAG_TOKEN'

Выполните пять анонимных запросов:

for i in 1 2 3 4 5; do
  curl -sS -o /dev/null \
    --connect-timeout 10 \
    --max-time 30 \
    -w "run=$i http=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n" \
    "$BASE_URL/"
done

curl выводит накопительные значения от начала запроса:

DNS                     = time_namelookup
TCP connect             = time_connect - time_namelookup
TLS                     = time_appconnect - time_connect
ожидание первого байта  = time_starttransfer - time_appconnect
получение тела           = time_total - time_starttransfer

На что смотреть:

  • одинаков ли HTTP-статус;
  • отличаются ли первый и последующие запросы;
  • остаётся ли высоким time_starttransfer;
  • насколько time_total больше TTFB;
  • возникает ли редирект вместо страницы.

Если первый запрос медленный, а следующие резко ускоряются, это похоже на рабочий page cache. Если все пять запросов остаются одинаково медленными, готовая страница, вероятно, не используется.

Шаг 2. Проверьте прямую отдачу статического файла

curl -sS -o /dev/null \
  --connect-timeout 10 \
  --max-time 30 \
  -w 'http=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
  "$BASE_URL/diag-static.txt"

Ожидаемое тело файла:

wordpress-ttfb-diagnostic-static-ok

Интерпретация:

  • страница и статический файл медленные — рано переходить внутрь WordPress; проверяйте сеть, веб-сервер и инфраструктуру;
  • статический файл быстрый, страница медленная — базовая отдача работает, задержка находится в динамическом контуре;
  • статический URL возвращает 301, 302 или HTML — запрос проходит не напрямую, тест нужно исправить.

Эта проверка не доказывает, что сервер никогда не перегружен. Она показывает, способен ли тот же контур в тот же момент быстро отдать простой файл.

Шаг 3. Отделите чистый PHP от WordPress

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

curl -sS \
  --connect-timeout 10 \
  --max-time 30 \
  -H "X-Diag-Token: $TOKEN" \
  "$BASE_URL/diag-php.php"

Пример ответа:

{
  "ok": true,
  "test": "plain_php",
  "php_version": "8.3.8",
  "php_sapi": "fpm-fcgi",
  "script_runtime_ms": 0.18,
  "memory_peak_mb": 2
}

Затем измерить внешний TTFB этого же PHP-файла:

curl -sS -o /dev/null \
  --connect-timeout 10 \
  --max-time 30 \
  -H "X-Diag-Token: $TOKEN" \
  -w 'http=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
  "$BASE_URL/diag-php.php"

Сравниваются два числа:

  • script_runtime_ms — сколько выполнялся сам PHP-код;
  • внешний TTFB — сколько клиент ждал первый байт.

Если PHP-код выполняется меньше миллисекунды, а TTFB составляет 500–1000 мс, задержка находится до или вокруг выполнения скрипта: очередь PHP-FPM, запуск worker, веб-сервер, FastCGI, файловая система или инфраструктура хостинга.

Если статический файл и чистый PHP быстрые, а WordPress-страница медленная, область поиска сужается до CMS.

Шаг 4. Измерьте bootstrap WordPress без шаблона

diag-wordpress-bootstrap.php загружает wp-load.php, активные плагины и функции темы, но не запускает обычный рендеринг страницы.

curl -sS \
  --connect-timeout 10 \
  --max-time 30 \
  -H "X-Diag-Token: $TOKEN" \
  "$BASE_URL/diag-wordpress-bootstrap.php"

Пример ответа:

{
  "ok": true,
  "test": "wordpress_bootstrap",
  "wordpress_version": "6.x",
  "wordpress_bootstrap_ms": 684.27,
  "total_script_ms": 684.62,
  "memory_peak_mb": 42
}

Интерпретация:

  • чистый PHP быстрый, bootstrap медленный — задержка возникает при загрузке WordPress, плагинов, theme functions, autoloaded options, файлов или внешних вызовов;
  • bootstrap заметно быстрее полной страницы — дополнительное время появляется в шаблоне, виджетах, shortcode, запросах темы или генерации HTML;
  • bootstrap нестабилен — ищите периодические внешние обращения, cron, файловые блокировки или нагрузку.

Для серии запросов:

for i in 1 2 3 4 5; do
  curl -sS \
    -H "X-Diag-Token: $TOKEN" \
    "$BASE_URL/diag-wordpress-bootstrap.php"
  echo
done

Шаг 5. Измерьте долю базы данных

diag-wordpress-db.php включает SAVEQUERIES до загрузки WordPress, но возвращает только агрегированные показатели. Тексты запросов и call stack наружу не выводятся.

curl -sS \
  --connect-timeout 10 \
  --max-time 30 \
  -H "X-Diag-Token: $TOKEN" \
  "$BASE_URL/diag-wordpress-db.php"

Пример:

{
  "wordpress_bootstrap_ms": 700.2,
  "query_count": 65,
  "query_time_ms": 12.1,
  "slowest_query_ms": 3.4,
  "non_query_time_ms": 688.1,
  "database_share_percent": 1.73
}

Ключевые поля:

Поле Что означает
wordpress_bootstrap_ms полное время загрузки WordPress
query_count количество SQL-запросов во время bootstrap
query_time_ms суммарное время SQL
slowest_query_ms самый долгий запрос без вывода его текста
non_query_time_ms время bootstrap за вычетом SQL
database_share_percent доля SQL в общем времени

Если WordPress загружается 700 мс, а SQL занимает 12 мс, база участвует в запросе, но не объясняет основную задержку. В таком случае искать нужно в PHP-коде, hooks, файловой системе, плагинах или внешних HTTP-вызовах.

Если доля базы велика или один запрос заметно выделяется, следующий инструмент — Query Monitor, APM или slow query log. Этот набор намеренно не публикует SQL-тексты и не пытается заменить полноценный профилировщик.

Количество запросов само по себе не является диагнозом. Сто быстрых запросов могут занимать меньше времени, чем один тяжёлый.

Шаг 6. Проверьте page cache по фактическому поведению

Включённый переключатель Page Cache показывает конфигурацию, но не гарантирует, что посетитель получает готовый HTML.

Проверка должна пройти всю цепочку:

плагин активен
→ page cache включён
→ кеш сформирован
→ URL не исключён
→ запрос подходит под правила
→ повторный посетитель получает готовую страницу

Сначала выполните пять одинаковых анонимных запросов:

for i in 1 2 3 4 5; do
  curl -sS -o /dev/null \
    --connect-timeout 10 \
    --max-time 30 \
    -w "run=$i http=%{http_code} ttfb=%{time_starttransfer} total=%{time_total}\n" \
    "$BASE_URL/"
done

После очистки кеша повторите ту же команду.

Ожидаемый сценарий рабочего кеша:

первый запрос:  1.4 с
второй запрос:  0.1 с
третий запрос:  0.1 с

Если все запросы остаются одинаково медленными, возможны разные причины:

  • страница исключена из кеширования;
  • запрос содержит исключающие cookies;
  • пользователь авторизован;
  • query parameters обходят кеш;
  • кеш не создаётся;
  • кеш постоянно инвалидируется;
  • правила веб-сервера не отдают готовый файл;
  • плагин конфликтует с окружением.

Это направления проверки, а не готовый диагноз.

Посмотрите диагностические заголовки

curl -sS \
  -D - \
  -o /dev/null \
  "$BASE_URL/" \
  | grep -Ei '^(HTTP/|cache-control:|age:|x-cache:|x-cache-status:|cf-cache-status:|server-timing:)'

Не каждый плагин добавляет заголовок cache hit. Отсутствие X-Cache не доказывает, что кеш не работает. Главным критерием остаётся повторяемое снижение TTFB.

Как читать результаты

Статика Чистый PHP WordPress Повторная страница Вероятная область поиска
Медленно Медленно Медленно Медленно сеть, веб-сервер, PHP-FPM, ресурсы хостинга
Быстро Медленно Медленно Медленно очередь PHP, worker limits, FastCGI, файловая система
Быстро Быстро Медленно Медленно WordPress, плагины, тема, внешние вызовы
Быстро Быстро Медленно первый раз Быстро page cache работает, дорогой cache miss
Быстро Быстро Медленно всегда Медленно page cache не используется или постоянно сбрасывается
Нестабильно Нестабильно Нестабильно Нестабильно периодическая нагрузка или инфраструктурная проблема

Матрица не заменяет профилирование. Она показывает, какой инструмент нужен следующим.

Что стоит добавить в PING

Ручная диагностика полезна для расследования, но её не стоит повторять при каждой аномалии. Сейчас PING хранит итоговое время ответа. Следующая итерация может сохранять составляющие отдельно:

Поле Что показывает
dns_ms разрешение доменного имени
connect_ms установку TCP-соединения
tls_ms TLS-рукопожатие
ttfb_ms время до первого байта от начала запроса
server_wait_ms интервал от окончания TLS до первого байта
transfer_ms получение доступной части ответа
total_ms полное время проверки

server_wait_ms не является чистым временем WordPress, но помогает сразу увидеть, что DNS и TLS не изменились, а задержка появилась после установления защищённого соединения.

Такое расширение не превращает PING в APM и не определяет медленный плагин автоматически. Оно сокращает путь до следующей проверки.

Практический пример: включённый кеш не давал ускорения

В рабочем кейсе PING заметил, что WordPress-сайт стабильно отвечает за 1,5–2,5 секунды при 200 OK.

Участок Результат
Статический файл около 0,1 с
Чистый PHP около 0,10–0,13 с
WordPress bootstrap около 0,62–0,74 с
SQL внутри bootstrap обычно 8–10 мс
Полная страница около 1,4–1,6 с в ручной серии, периодически выше

W3 Total Cache был активен, Page Cache — включён. Но повторные анонимные запросы не ускорялись. После очистки всех кешей и повторного формирования страницы время ответа сократилось примерно до 90–120 миллисекунд.

Точная причина прежнего состояния кеша не была установлена. Подтверждённого вывода было достаточно: до очистки повторные запросы не получали ожидаемого эффекта page cache, после неё — начали получать.

Меняйте по одному фактору

Диагностика теряет смысл, если одновременно:

  • обновить PHP;
  • отключить несколько плагинов;
  • сменить тему;
  • очистить кеш;
  • включить CDN;
  • изменить настройки базы.

Даже если сайт ускорится, причина останется неизвестной.

Рабочий цикл:

зафиксировать baseline
→ изменить один фактор
→ повторить тот же тест
→ сравнить этапы
→ принять или откатить изменение

Для периодической проблемы внешний мониторинг полезнее разовой проверки: он показывает, сохранился ли эффект через несколько часов.

Когда этого подхода недостаточно

Послойная HTTP-диагностика отвечает на вопрос, где возникает задержка до получения HTML. Другие инструменты нужны, если:

  • HTML приходит быстро, но страница долго отрисовывается из-за JavaScript и ресурсов;
  • проблема возникает только у авторизованных пользователей;
  • медленно работает административная панель;
  • задержка проявляется только в корзине или оформлении заказа;
  • внешний API тормозит периодически;
  • проблема возникает только под нагрузкой;
  • сайт медленный только у конкретного провайдера или региона;
  • нужно найти конкретный медленный hook, функцию или SQL-запрос.

В зависимости от ситуации понадобятся browser monitoring, Query Monitor, PHP-профилировщик, APM, slow query log, нагрузочный тест или дополнительная точка наблюдения.

Риски и обязательная очистка

Удалите файлы сразу после теста

С сервера должны быть удалены:

diag-config.php
diag-static.txt
diag-php.php
diag-wordpress-bootstrap.php
diag-wordpress-db.php

Даже защищённые токеном диагностические endpoints не должны оставаться в production.

SAVEQUERIES добавляет накладные расходы

В диагностическом файле он действует только в рамках одного запроса. Не включайте SAVEQUERIES постоянно на нагруженном production без отдельной причины.

Быстрый page cache не означает быстрый WordPress

Cache hit может обходить большую часть CMS. Административная панель, авторизованные пользователи и первый запрос после очистки могут оставаться медленными.

Одна точка не описывает всех пользователей

Проверка из дата-центра не воспроизводит домашнюю, мобильную или региональную сеть. Если задержка зависит от маршрута, сравнивайте несколько независимых точек.

Чек-лист

Вывод

Высокий TTFB WordPress лучше диагностировать не по названию CMS и не по списку активных плагинов, а по границам системы. Статический файл, чистый PHP, WordPress bootstrap, SQL и повторный кешированный запрос дают контрольные точки, между которыми можно локализовать задержку.

Сначала нужно доказать, на каком слое теряется время, и только затем менять настройки этого слоя.

Готовый набор файлов делает этот процесс воспроизводимым: один и тот же тест можно выполнить до изменения, сразу после него и через несколько часов.

Что дальше?

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