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

Не начинайте с «поставьте кэш-плагин». Для site.example пройдите слои по порядку: DNS (время резолва, кэш, неверный IP), TCP/TLS (handshake, сертификат, HTTP/2), TTFB (PHP-FPM/Apache генерация), SQL (locks, slow query), ассеты (вес, число запросов, без сжатия, чужие CDN). Сравнивайте curl --resolve на 10.0.20.40 и «как пользователь» через публичный DNS. Оперативные симптомы TTFB: сайт медленно отвечает. Таймауты: 504.

Зафиксируйте цифры до любых правок.

Фиксируйте цифры до правок: иначе не отличите регресс PHP от «у клиента плохая сеть».

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

  • «Долго, потом щёлк» — часто TTFB.
  • «Страница пустая, потом рисуется» — ассеты/JS.
  • «То быстро то нет» — очередь FPM, боты, lock SQL.
  • Только с мобильного — сеть/TLS 1-RTT / DNS оператора.

Таблица решений — в конце диагностики, не в начале.

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

  1. DNS: высокий TTL плюс далёкий резолвер, NXDOMAIN→поиск, CNAME-цепочка, SERVFAIL retry.
  2. TLS: полный handshake каждый раз без session, слабый CPU, неверный chain (клиент ретраит).
  3. TTFB: PHP без opcache, холодный WP, max_children очередь, диск.
  4. SQL: полный scan, missing index, autocommit транзакции.
  5. Ассеты: 20 МБ героя, блокирующий JS, нет gzip/brotli, 100 запросов без HTTP/2.
  6. Боты: перегруз.
  7. 502/очередь: FPM, 502.

Диагностика

Плейсхолдеры: site.example, 10.0.20.40, /var/www/site.example.

1. DNS

dig +stats A site.example
dig +stats AAAA site.example
dig +trace site.example | tail

Смотрите Query time. Сравните с dig @<NS_из_ответа>. Если A не 10.0.20.40 (при прямом origin) — сначала DNS.

curl -o /dev/null -s -w 'namelookup:%{time_namelookup}\n' https://site.example/
curl -o /dev/null -s -w 'namelookup:%{time_namelookup}\n' --resolve site.example:443:10.0.20.40 https://site.example/

Второй обнуляет DNS. Разница — резолв.

2. TCP и TLS

curl -o /dev/null -s -w 'connect:%{time_connect} tls:%{time_appconnect}\n' --resolve site.example:443:10.0.20.40 https://site.example/
echo | openssl s_client -connect 10.0.20.40:443 -servername site.example -tls1_2 2>/dev/null | grep -E 'Protocol|Cipher'

time_appconnect >> time_connect — CPU/крипто/прокси. Проверьте, нет ли лишнего hop NPM+nginx+apache.

HTTP/2:

curl -sI --http2 --resolve site.example:443:10.0.20.40 https://site.example/ | head

3. TTFB HTML

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

Повторите 5 раз. Статика /favicon.ico vs /. Если favicon 20 мс, HTML 2 с — генерация, не канал.

Включите $request_time $upstream_response_time в лог nginx на время инцидента.

PHP slowlog — как в статье TTFB. opcache_get_status только с localhost, не светите в интернет.

4. SQL

На время:

SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW FULL PROCESSLIST;

Slow query log long_query_time=1. Ищите запросы темы/плагина из стека FPM.

5. Диск

iostat -xz 1 3
df -h /var/www /var/lib/mysql

Backup на том же диске в рабочее время — классика.

6. Ассеты

curl -s --resolve site.example:443:10.0.20.40 https://site.example/ | grep -oE 'src="[^"]+' | head

В DevTools: Waterfall, что блокирует. Content-Encoding: gzip на text/css и JS:

curl -sI -H 'Accept-Encoding: gzip' --resolve site.example:443:10.0.20.40 https://site.example/wp-includes/css/dashicons.min.css | grep -i content-encoding

Путь CSS подставьте реальный из HTML.

7. Сравнение CDN vs origin

--resolve на origin IP vs обычный URL. Если CDN медленнее origin — кэш miss/гео. Если origin медленный — CDN HTML-кэш только спрячет.

Решение

  • DNS: TTL, лишние CNAME, битый AAAA — DNS старый сервер если IP не тот.
  • TLS: HTTP/2, актуальные протоколы, не слабые шифры ради «старых XP». Stapling опционально.
  • TTFB: opcache, object cache, меньше плагинов, очередь FPM, вынести wp-cron.
  • SQL: индексы, убрать N+1, не SELECT *.
  • Ассеты: сжатие nginx gzip on для text, лимиты картинок, не 4 МБ PNG в шапке.
  • Боты: лимиты до PHP.

Не включайте Xdebug и display_errors для перф-профилирования в проде.

gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;

nginx -t && reload.

Сценарий: разложить HAR

Скачайте HAR с проблемного клиента. Смотрите: (1) время до первого запроса после DNS, (2) TTFB документа, (3) длинные waiting, (4) блокирующий JS в <head>, (5) 4xx/5xx ассетов с ретраями. Ретраи TLS увеличивают appconnect. Сверьте URL ассетов: если они на старом CDN/IP — DNS.

С сервера:

curl -s -o /dev/null -w '%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n' \
  --resolve site.example:443:10.0.20.40 https://site.example/
php -r 'echo "cli\n";'  # не TTFB сайта

Снимите EXPLAIN на запрос из slow log, не гадайте индекс. Если iowait высокий во время TTFB — сначала диск/backup, не Redis.

Документируйте: «TTFB origin 1.8s, DNS 20ms, TLS 30ms, LCP 4s из 2.5MB hero». Это разделяет работы системного и фронтенда. Не смешивайте в одном тикете «сайт медленный».

HTTP/2 мультиплекс не лечит TTFB HTML. Он лечит пачку мелких ассетов на одном соединении. Измерьте оба слоя отдельно.

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

Повторите тот же набор curl до/после, запишите namelookup, connect, appconnect, starttransfer, total. LCP в полевом RUM, не только ваш офисный гигабит. Проверьте пик: p95 request_time за час.

Страница 200, не 504. FPM не на max_children без ботов.

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

  • Регион пользователя далек — нужен CDN ассетов, HTML всё ещё origin.
  • Steal time гипервизора.
  • MySQL на другой площадке с RTT 40 мс × десятки запросов.
  • HTTP/3 только на CDN, origin HTTP/1.1 — измерьте оба.

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

  • Чёрный ящик: cron curl с --resolve и без, алерт p95.
  • Бюджет: TTFB HTML < оговорённого SLA.
  • Нагрузочный тест на staging, не на проде в обед.
  • Каталог плагинов без «на всякий случай».
  • Храните эталон HAR «хорошего дня» для site.example с --resolve на 10.0.20.40: иначе спорите о регрессе без цифр namelookup/connect/ttfb.

FAQ

PageSpeed 100 обязателен?

Нет. Сначала бизнес-метрика (TTFB, LCP вашей аудитории). Балл — эвристика.

ab / wrk на прод?

Осторожно, это DoS себе. Только окно и лимит RPS.

Почему ping хороший, TTFB плохой?

Ping ≠ TLS+PHP+SQL.

Нужен ли Redis сразу?

После доказательства, что object cache поможет (много повторяемых опций WP). Не вместо индекса.

Apache mod_pagespeed

Снят с поддержки в привычном виде. Не тащите как стандарт 2026. Кэш и сжатие — nginx/CDN.