Пинг 20 мс, а страница грузится 4 секунды: где на самом деле уходит время
Клиент открывает терминал, гоняет ping до сервера, видит честные 20 мс — и тут же пишет в поддержку: «у вас же отличная связь, почему сайт грузится четыре секунды?». Вопрос закономерный, но построен на неверной посылке: ping и загрузка страницы в браузере измеряют совершенно разные вещи, и низкая цифра в одном не гарантирует ничего в другом. Разбираем по шагам, куда реально уходит время между «сервер ответил на ICMP-пакет» и «страница отрисовалась в браузере».
Содержание
Пинг меряет не то, что грузит страницу
ping отправляет ICMP-пакет (echo request) и замеряет, сколько времени тот идёт туда и обратно — это чистая величина RTT (round-trip time) на сетевом уровне, без всякого отношения к тому, что происходит выше. ICMP — не тот протокол, которым пользуется браузер: страница грузится по HTTP поверх TCP (или QUIC поверх UDP для HTTP/3), а перед этим ещё нужно узнать IP-адрес через DNS и установить шифрованное соединение через TLS. Ни один из этих шагов ping не выполняет и не видит.
Более того, на части маршрутов ICMP-трафик обрабатывается иначе, чем обычный TCP/UDP-трафик — некоторые операторы намеренно понижают его приоритет или ограничивают частоту ответов, потому что это служебный протокол, а не пользовательский. Из-за этого цифра ping иногда бывает даже немного оптимистичнее реальной задержки TCP-соединения.
Но главное не в этом. Даже если бы ping абсолютно точно показывал сетевую задержку до сервера, страница состоит не из одного пакета — это цепочка последовательных этапов, у каждого из которых свой RTT и своя вероятность затормозить. Низкий пинг означает только одно: труба между вами и сервером короткая и без больших заторов на сетевом уровне. Он ничего не говорит о том, что происходит дальше — на прикладном уровне, где живут DNS, TLS, бэкенд и браузер. Про разницу задержки сети и пропускной способности канала — отдельной, но смежной характеристики — подробно разобрано в статье про разницу задержки и полосы канала, она хорошее дополнение к этому материалу.
Полная цепочка: что происходит между кликом и первым байтом
Прежде чем браузер получит хотя бы один байт HTML, он должен пройти несколько последовательных этапов, и каждый добавляет своё время поверх сетевой задержки:
- DNS-резолвинг — превратить доменное имя в IP-адрес. Если ответ не в локальном кеше, это отдельный сетевой запрос (иногда цепочка из нескольких) к DNS-серверам, до того как браузер вообще узнает, куда стучаться.
- TCP-хендшейк — трёхстороннее рукопожатие (SYN → SYN-ACK → ACK), минимум один полный RTT, прежде чем соединение установлено.
- TLS-хендшейк — для HTTPS поверх уже установленного TCP-соединения идёт согласование шифрования: TLS 1.3 в лучшем случае укладывается в дополнительный один RTT, TLS 1.2 обычно требует двух.
- Отправка запроса и ожидание первого байта ответа (TTFB) — браузер отправил HTTP-запрос, а сервер должен его обработать: поднять сессию, сходить в базу, собрать шаблон и начать отдавать ответ.
- Загрузка остальных ресурсов страницы — CSS, JS, шрифты, картинки, трекеры аналитики; здесь решает не задержка сама по себе, а то, сколько из этих запросов идёт последовательно, а сколько параллельно.
- Разбор и отрисовка — браузеру ещё нужно распарсить HTML, построить DOM и CSSOM, выполнить блокирующие скрипты и отрисовать страницу.
Даже при идеальных 20 мс сетевой задержки шаги 1-3 суммарно дают 3-4 RTT ещё до первого байта — это 60-80 мс, немного, но заметно на фоне «ping хороший». А вот шаги 4 и 5 — это уже не про сеть вообще, и именно там чаще всего прячутся секунды, которые видит клиент, когда пишет «у вас пинг 20 мс, а грузится четыре секунды».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверTTFB: где сервер сам съедает секунды
Time To First Byte (TTFB) — время от отправки запроса до получения первого байта ответа. Это сумма сетевой задержки (тех самых RTT на TCP/TLS) и времени, которое сервер потратил на обработку запроса. Если TTFB большой, а ping при этом маленький — почти наверняка проблема не в сети, а в том, как долго бэкенд собирает ответ.
Типичные причины медленного TTFB на самом сервере:
- Медленные запросы к базе данных — отсутствующий индекс, N+1-запросы, блокировки на таблице под нагрузкой.
- Отсутствие кеширования на уровне приложения — каждая загрузка заново собирает одинаковый контент из базы, вместо того чтобы отдать закешированный результат.
- Синхронные вызовы внешних API — если генерация страницы ждёт ответа стороннего сервиса (платёжный шлюз, геолокация, курс валют) прямо в процессе рендера, его задержка напрямую добавляется к TTFB.
- Нехватка воркеров — если пул PHP-FPM, Node.js-процессов или воркеров приложения исчерпан, запрос встаёт в очередь и ждёт освобождения воркера.
- Холодный старт — для serverless-функций или приложений с ленивой инициализацией первый запрос после простоя заметно медленнее последующих.
Важно отделить TTFB от сетевой части. Если у вас ping 20 мс, а TTFB — 800 мс, эти 800 мс почти целиком на совести бэкенда: сама передача запроса и начала ответа при такой задержке должна укладываться в десятки миллисекунд.
Один пинг — это один RTT, а страница — это десятки запросов
Даже если TTFB в порядке, страница редко состоит из одного файла. Обычный сайт — это HTML плюс десятки, а иногда и сотни отдельных ресурсов: стили, скрипты, шрифты, иконки, картинки, счётчики аналитики. И вот тут низкая задержка перестаёт спасать, если запросы идут не так, как могли бы.
Блокирующие скрипты и стили. Если <script> без атрибутов async/defer стоит в <head>, браузер останавливает разбор HTML, пока не скачает и не выполнит этот скрипт полностью. То же с CSS, подключённым в <head> без разбивки на критический и некритический — браузер не начнёт отрисовку, пока не получит все стили.
Цепочки зависимостей. Часто один скрипт триггерит загрузку следующего (аналитика подгружает ещё один трекер, тот — ещё один), и браузер физически не может начать качать ресурс Б, пока не выполнит скрипт А, который сообщает адрес ресурса Б. Каждое такое звено — это ещё один RTT в лучшем случае, и они не параллелятся, а суммируются.
Отсутствие keep-alive. Если сервер закрывает TCP-соединение после каждого ответа вместо того, чтобы держать его открытым, браузеру приходится заново проходить TCP- и TLS-хендшейк на каждый ресурс — то есть платить те самые 2-4 RTT снова и снова, вместо того чтобы заплатить их один раз и переиспользовать соединение.
HTTP/1.1 без мультиплексирования. По одному TCP-соединению в HTTP/1.1 браузер может отправить только один запрос за раз, и параллелит загрузку за счёт открытия нескольких соединений одновременно — обычно 6 на домен. Если ресурсов больше, часть встаёт в очередь и ждёт освобождения соединения. HTTP/2 решает это мультиплексированием запросов внутри одного TCP-соединения, но добавляет свою тонкость: все потоки внутри такого соединения зависят от общей доставки пакетов, и при потере пакета на плохом канале это может аукнуться всем запросам сразу. Механика этого эффекта разобрана в статье про мультиплексирование HTTP/2 и почему один медленный ответ тормозит остальные.
Итог этого раздела простой: 20 мс пинга помножьте не на один запрос, а на реальное число последовательных зависимостей на странице — и станет понятно, откуда берутся секунды даже при формально отличной сети.
Размер ответа, сжатие и кеш браузера
Ещё два фактора, не связанных с задержкой напрямую, но регулярно превращающие «хороший пинг» в «медленную страницу».
Сжатие. Если сервер не отдаёт HTML, CSS и JS со сжатием (gzip или, лучше, brotli), клиент скачивает в разы больше байт, чем нужно — текстовые форматы сжимаются очень хорошо, и разница может быть кратной. Проверить легко:
curl -s -D - -o /dev/null -H "Accept-Encoding: gzip, br" https://example.com | grep -i content-encoding
Если в ответе нет заголовка Content-Encoding: gzip или br — сжатие не работает, и это первое, что стоит включить на веб-сервере (в nginx — директивы gzip on и модуль brotli, если он собран).
Несжатые изображения и видео. Картинки в оригинальном разрешении из фотоаппарата, без ресайза под реальный размер блока на странице, без современных форматов (WebP, AVIF) — частая причина, когда страница весит мегабайты вместо сотен килобайт. Полоса пропускания при этом ни при чём: канал может быть широким, но если файл в 10 раз тяжелее необходимого, время его передачи вырастет пропорционально.
Отсутствие кеширования на клиенте. Если сервер не отдаёт заголовки Cache-Control и ETag (или отдаёт их с нулевым временем жизни), браузер заново скачивает статику при каждом визите вместо того, чтобы взять её из локального кеша. Это отвечает на частый вопрос «почему при повторном заходе всё так же медленно»: без правильных заголовков кеша повторный визит ничем не отличается от первого.
Это же путают с мифом «просто повесь CDN, и всё ускорится». CDN снижает задержку за счёт географической близости точки раздачи, но не заменяет ни сжатие, ни правильные заголовки кеша — если отдавать с CDN несжатые мегабайтные картинки без кеш-заголовков, выигрыш будет куда скромнее ожидаемого.
Как искать узкое место: DevTools, waterfall и curl -w
Гадать бесполезно — все перечисленные выше этапы можно и нужно увидеть по отдельности.
DevTools Network tab (браузер). Откройте вкладку Network в инструментах разработчика (F12 или Cmd+Opt+I), включите «Disable cache» для честной проверки первой загрузки, обновите страницу. Каждая строка в списке запросов — это отдельный ресурс, а справа — цветная диаграмма (waterfall), где видно:
- Queueing — запрос ждал в очереди браузера, пока не освободится соединение (типичный симптом нехватки параллельных соединений на HTTP/1.1).
- DNS Lookup — время резолвинга имени.
- Initial connection — TCP-хендшейк.
- SSL — TLS-хендшейк, если это HTTPS.
- TTFB (Waiting) — от отправки запроса до первого байта ответа: это тот самый TTFB, о котором шла речь выше.
- Content Download — собственно передача тела ответа.
Наведите курсор на любой запрос — всплывающая подсказка покажет точную разбивку по этим этапам в миллисекундах. Если у большинства ресурсов длинная зелёная/жёлтая полоска Queueing — проблема в параллелизме запросов (мало соединений или их не переиспользуют). Если у главного HTML-документа длинный сегмент Waiting — проблема на бэкенде, а не в сети. Если после TTFB долго тянется Content Download при небольшом объёме — вероятно, дело в отсутствии сжатия или в узкой полосе канала.
Дополнительно вкладка Network показывает общую сводку внизу: число запросов, суммарный вес страницы и время до событий DOMContentLoaded и Load. Если запросов заметно больше полусотни на простую страницу — уже само по себе повод пересмотреть, что из этого реально нужно грузить синхронно.
curl -w для точного разбора одного запроса. Быстрее и без браузера — командная строка:
curl -o /dev/null -s -w \
"DNS: %{time_namelookup}s\n\
TCP connect: %{time_connect}s\n\
TLS handshake: %{time_appconnect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n" \
https://example.com
Читать результат так же, как и waterfall в DevTools:
time_namelookupбольшой — тормозит DNS, стоит проверить резолвер или TTL записей.- Разница между
time_connectиtime_namelookup— это чистое время TCP-хендшейка, сравнимое сping, но для TCP, а не ICMP. - Разница между
time_appconnectиtime_connect— сколько стоит именно TLS. Для сервера, физически далёкого от клиента, или при неоптимальной цепочке сертификатов (лишний OCSP-запрос, длинная цепочка промежуточных сертификатов) эта разница может быть заметно больше одного RTT. - Разница между
time_starttransferиtime_appconnect— это и есть время работы бэкенда, то есть чистый TTFB на стороне сервера, без сетевой составляющей. Если оно большое при маленькомping— ищите проблему в приложении, а не в сети. - Разница между
time_totalиtime_starttransfer— время передачи самого тела ответа, зависящее от размера страницы и полосы канала.
Если нужно повторить один и тот же запрос несколько раз для усреднения (одно измерение может случайно попасть на всплеск нагрузки), удобно обернуть в цикл:
for i in $(seq 1 10); do
curl -o /dev/null -s -w "%{time_starttransfer} %{time_total}\n" https://example.com
done
Куда смотреть в первую очередь. Если и ping, и time_connect/time_appconnect в норме, а долго только time_starttransfer — проблема на сервере: смотрите логи медленных запросов к базе, профилируйте бэкенд, проверяйте пул воркеров. Если TTFB нормальный, а общее время загрузки страницы большое — смотрите в DevTools на количество запросов и их параллелизм: скорее всего дело в блокирующих скриптах, отсутствии keep-alive или в том, что ресурсов слишком много и они не сжаты. Если конкретно сетевые этапы (DNS/TCP/TLS) неожиданно велики при в целом хорошем ping — возможно, дело в самом маршруте до сервера, а не в его загруженности; общая логика того, как маршрут может не совпадать с ожиданиями от географии и пинга, разобрана в статье про пиринг и транзит простыми словами.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Ping 20 мс, а curl показывает TTFB 1.5 секунды — это точно проблема сервера, а не сети?
Практически наверняка да. Сетевая часть запроса (DNS + TCP + TLS) при пинге 20 мс укладывается в десятки, максимум — в первые сотни миллисекунд. Если time_starttransfer в разы больше — время ушло на обработку на сервере: медленный SQL-запрос, отсутствие кеша, ожидание внешнего API или нехватка воркеров.
Может ли VPN или прокси клиента влиять на такую разницу?
Да, VPN добавляет хоп и иногда собственную обработку трафика, что увеличивает и ping, и время TCP/TLS-этапов. Но если разница видна именно на TTFB или на числе последовательных запросов — VPN тут не главная причина, стоит проверить отдельно, сравнив измерения с ним и без.
HTTP/2 или HTTP/3 сами по себе решат проблему медленной загрузки?
Частично. Они убирают лишние TCP/TLS-хендшейки за счёт переиспользования соединения и мультиплексируют запросы, так что зависимость от числа мелких файлов становится слабее. Но они не ускорят медленный бэкенд и не сожмут несжатые картинки — это отдельные задачи.
Стоит ли доверять одному замеру curl или ping, или нужно мерить несколько раз?
Нужно мерить несколько раз и в разное время — один запрос может случайно попасть на всплеск нагрузки, затор на узле сети или холодный кеш. Разумный минимум — 10-20 повторов подряд и пара замеров в разное время суток.
Если DevTools показывает нормальный TTFB, но долгий Content Download — в чём дело?
Скорее всего в размере ответа: либо ресурс слишком тяжёлый (несжатое изображение, крупный JS-бандл), либо у сервера или клиента узкий канал на этом участке маршрута. Сначала проверьте сжатие (Content-Encoding в заголовках ответа) и реальный вес файла, прежде чем разбираться с полосой.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →