Короткий ответ

Сначала измерьте, где теряется время: DNS, TCP/TLS, TTFB (сервер думает), загрузка ассетов. Для site.example на 10.0.20.40 используйте curl -w с --resolve, чтобы не смешать DNS и origin. Если TTFB 8 с, а картинки по 50 мс — кэш CloudFlare не лечит PHP/SQL. Если TTFB 80 мс, а 4 МБ JS — это фронт, не FPM.

Полная цепочка (DNS→TLS→SQL→ассеты) разобрана в диагностике медленного сайта. Здесь — оперативный разбор «сервер отвечает долго» на Ubuntu + nginx/Apache + PHP-FPM 8.2/8.3.

Симптомы и как отличить

  • Пользователи: «висит, потом открывается».
  • Админка WP медленная, витрина быстрая (или наоборот).
  • Под ботами всё деградирует: боты.
  • Иногда 504: TTFB упёрся в timeout.
ИзмерениеСлой
time_namelookup большойDNS резолвера клиента
time_connect большойсеть до 10.0.20.40
time_appconnect большойTLS, CPU, handshake
time_starttransfer большой, остальное малоPHP/SQL/диск/upstream
total ≈ transfer, TTFB малтяжёлое тело ответа

Возможные причины

  1. SQL без индекса, N+1 в теме WordPress.
  2. object-cache отсутствует, каждый хит бьёт БД.
  3. PHP-FPM очередь: max_children, воркеры заняты ботами/cron HTTP.
  4. Диск: iowait, заполненный /, backup в тот же том.
  5. Апстрим proxy_pass на медленный API без таймаута чтения у приложения.
  6. wp-cron на каждом визите.
  7. Xdebug в проде (не должен быть).
  8. NFS webroot с latency.

Не путать с «первый байт быстрый, LCP плохой» — тогда DevTools, не pm.max_children.

Диагностика

1. TTFB с разделением фаз

curl -o /dev/null -s -w '\
 namelookup:%{time_namelookup}\
 connect:%{time_connect}\
 appconnect:%{time_appconnect}\
 starttransfer:%{time_starttransfer}\
 total:%{time_total}\
 http:%{http_code}\n' \
  --resolve site.example:443:10.0.20.40 https://site.example/

Повторите 5 раз: холодный vs тёплый opcache.

С localhost:

curl -o /dev/null -s -w 'ttfb:%{time_starttransfer} total:%{time_total}\n' \
  --resolve site.example:443:127.0.0.1 https://site.example/

2. Access log request_time

log_format timed '$remote_addr $status $request_time $upstream_response_time $uri';

После nginx -t и reload смотрите p95. Сохраните сниппет в бэкапе конфига.

sudo awk '$9==200 {s+=$10; n++} END {print n, s/n}' /var/log/nginx/access.log

Формат combined кладёт время не в $10 — сверяйте свой log_format. На combined время не пишется; добавьте $request_time.

3. PHP slowlog

slowlog = /var/log/php8.3-fpm-slow.log
request_slowlog_timeout = 3s
sudo systemctl reload php8.3-fpm
sudo tail -n 40 /var/log/php8.3-fpm-slow.log

Стек покажет файл темы/плагина. Выключите или поднимите порог после сессии, чтобы не забить диск.

4. SQL

SHOW FULL PROCESSLIST;

Долгие Sending data / Locked. Включите slow query log на время (long_query_time = 2).

5. Диск и CPU

iostat -xz 1 5
top -b -n 1 | head -n 25
systemctl is-active php8.3-fpm mysql mariadb nginx

6. Очередь FPM

Лог max_children и ps по php-fpm. См. пул.

Решение

Сценарий A. TTFB высокий, SQL виноват

Индекс, лимит, кэш запросов приложения. Не поднимайте innodb_buffer_pool вслепую без free -m. Временный page cache плагина WP — костыль, пока чините запрос.

Сценарий B. PHP CPU, SQL тихий

Профилируйте плагин из slowlog. Отключите на копии стенда. Opcache должен быть включён в FPM:

sudo php-fpm8.3 -i | grep opcache.enable

opcache.enable=0 в проде — типичный «после отладки забыли».

Сценарий C. Диск

Перенесите backup с web-тома, ротация логов nginx. Не лечите iowait увеличением timeout (504).

Сценарий D. Очередь из ботов

limit_req + firewall, не бесконечный max_children.

Сценарий E. wp-cron

define('DISABLE_WP_CRON', true);

Системный cron:

echo '*/5 * * * * www-data php /var/www/site.example/wp-cron.php >/dev/null 2>&1' | sudo tee /etc/cron.d/site-example-wpcron

Сценарий F. Upstream

Таймауты и кэш ответа API. Не держите синхронный HTTP к мёртвому SaaS на каждом хите витрины.

Сценарий G. Opcache, JIT и «после деплоя медленно»

Первые запросы после systemctl reload php8.3-fpm холодные: opcache пуст. Это не регресс SQL. Прогрейте критичные URL с --resolve в хуке деплоя. JIT в PHP 8.3 для типичного WordPress обычно не даёт выигрыша TTFB, зато ест RAM; не включайте «потому что 8.3». opcache.validate_timestamps=0 на проде ускоряет, но требует reload FPM после выкладки — задокументируйте, иначе «обновил PHP-файл, сайт старый».

Сравните TTFB / и /wp-login.php: админка без page cache показывает честный PHP. Если витрина 80 мс, логин 4 с — кэш HTML маскирует проблему, SQL/плагины админки всё ещё тяжелые.

Как проверить, что проблема устранена

Тот же curl -w до/после, 5 итераций, сравнение starttransfer. Access log p95 за час без пика ботов. Админка и витрина отдельно: часто чинят одну сторону.

Функционально: страница 200, не 504.

Если не помогло

  • С сервера быстро, с мира медленно: канал, GEO, MTU, чужой DNS. Полная карта — статья диагностики.
  • Только HTTP/3/QUIC клиенты — отдельный профиль CDN.
  • Случайные пики: соседняя VM на гипервизоре, steal в top.
  • Redis object cache недоступен, WP молча идёт в MySQL — проверьте wp redis.
  • Админка медленная при быстрой витрине — page cache на анонимах маскирует PHP; мерьте /wp-admin/ отдельно тем же curl -w.

Профилактика

  • Бюджет TTFB в мониторинге (чёрный ящик curl с --resolve).
  • Slowlog порог 1–2 с на staging постоянно, на проде — по инциденту.
  • Ёмкость FPM посчитана, не «50 children на 1 ГиБ».

FAQ

CDN ускорит TTFB HTML у WordPress?

Кэш HTML — да, для анонимов. Personalized page всё равно ударит origin. Сначала измерьте origin.

gzip уменьшит TTFB?

Нет, чуть увеличит CPU до первого байта, уменьшит transfer. TTFB — про генерацию.

Стоит ли fastcgi_cache сразу?

После того как поняли hit/miss и cookie. Иначе закэшируете Set-Cookie сессии.

Apache mod_deflate vs nginx gzip на одном хосте

Один слой сжатия. Два — CPU впустую.

Почему ping 1 мс, сайт 5 с?

ICMP не TTFB. PHP/SQL не ходят в ping.