Короткий ответ
Сначала измерьте, где теряется время: 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 мал | тяжёлое тело ответа |
Возможные причины
- SQL без индекса, N+1 в теме WordPress.
object-cacheотсутствует, каждый хит бьёт БД.- PHP-FPM очередь:
max_children, воркеры заняты ботами/cron HTTP. - Диск:
iowait, заполненный/, backup в тот же том. - Апстрим
proxy_passна медленный API без таймаута чтения у приложения. wp-cronна каждом визите.- Xdebug в проде (не должен быть).
- 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 = 3ssudo 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 nginx6. Очередь 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.enableopcache.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.