---
title: 'Глубокая диагностика через curl: от «одного запроса» до мини‑бенчмарка | Zomro'
description: ''
image: 'https://zomro.com/static/site/images/logo/logo-white.png?v=1791909009'
---

![opened image]()

  * [Zomro](https://zomro.com/rus/)
  * [FAQ](https://zomro.com/rus/blog/faq/)
  * Глубокая диагностика через curl: от «одного запроса» до мини‑бенчмарка 



# Глубокая диагностика через curl: от «одного запроса» до мини‑бенчмарка

21-11-2025

В первой главе мы научились пользоваться `curl` как стетоскопом: один запрос — и сразу видно DNS, подключение, TLS, TTFB и общее время ответа.

Но в реальной жизни одного замера почти никогда недостаточно. Сегодня сайт может «стрельнуть» быстро один раз и провалиться на следующем. Нагрузка скачет, кэш прогревается, база иногда думает дольше. Поэтому во второй главе мы научимся:

  * прогонять много запросов подряд и считать среднее/проценты;

  * сравнивать IPv4 и IPv6;

  * стрелять в конкретный бэкенд за балансировщиком;

  * смотреть, как ведут себя редиректы и разные версии HTTP;

  * собирать из этого уже похожий на правду мини‑бенчмарк.




При этом я буду держать курс на то, чтобы даже начинающему админу было понятно, что и зачем делаем.

## Мини‑бенчмарк: многократные замеры и p95

Один запуск `curl` — это фотография. Нам нужна маленькая «видеозапись» поведения сайта.

Сделаем простой bash‑скрипт, который:

  * много раз дергает URL;

  * печатает только `time_total` (полное время);

  * в конце считает среднее и p95 (95‑й процент).




Создадим файл, например `curl-bench.sh`:
    
    
    #!/usr/bin/env bash
    
    URL="$1"
    COUNT="${2:-20}"  # default 20 requests
    
    if [[ -z "$URL" ]]; then
      echo "Usage: $0 <url> [count]"
      exit 1
    fi
    
    # collect timings into an array
    results=()
    
    for i in $(seq 1 "$COUNT"); do
      t=$(curl -s -o /dev/null -w '%{time_total}' "$URL")
      results+=("$t")
      echo "#${i}: ${t}s"
    done
    
    # sorting and calculating statistics
    sorted=$(printf '%s
    ' "${results[@]}" | sort -n)
    
    sum=0
    n=0
    
    while read -r v; do
      sum=$(awk -v s="$sum" -v v="$v" 'BEGIN {printf "%.6f", s+v}')
      n=$((n+1))
      vals+=("$v")
    done <<< "$sorted"
    
    avg=$(awk -v s="$sum" -v n="$n" 'BEGIN {printf "%.4f", s/n}')
    
    # p95 — element with index floor(0.95*n)
    idx=$(awk -v n="$n" 'BEGIN {printf "%d", (n*0.95)}')
    (( idx < 1 )) && idx=1
    
    p95=${vals[$((idx-1))]}
    
    echo "-------------------------"
    echo "requests: $n"
    echo "avg:      ${avg}s"
    echo "p95:      ${p95}s"
    

Делаем исполняемым:
    
    
    chmod +x curl-bench.sh
    

И запускаем:
    
    
    ./curl-bench.sh https://example.com/ 30
    

На выходе вы получите 30 строк вида `#N: 0.423s` и внизу краткую сводку:
    
    
    -------------------------
    requests: 30
    avg:      0.4100s
    p95:      0.6500s
    

### 

### Как это интерпретировать

  * **avg (среднее)** — средняя температура по больнице. Хорошо показывает общее состояние, но его может испортить один очень медленный запрос.

  * **p95** — 95‑й процент. 95% запросов быстрее этого значения. Именно p95 ближе всего к тому, «как сайт ощущается» пользователям.




Если avg 0.3s, а p95 1.2s — иногда у вас происходят всплески задержки (скорее всего, база/очереди/соседняя нагрузка).

## IPv4 vs IPv6: кто быстрее

Многие сайты уже отдают контент как по IPv4, так и по IPv6. Иногда один стек работает заметно медленнее другого.

Сравним:
    
    
    # IPv4
    curl -4 -s -o /dev/null -w @curl-format.txt https://example.com/
    
    # IPv6
    curl -6 -s -o /dev/null -w @curl-format.txt https://example.com/
    

На что смотрим:

  * если `time_connect` по IPv6 существенно больше — возможно, проблемы маршрутизации или фильтры по пути;

  * если `time_namelookup` сильно отличается — проверьте AAAA‑записи и настройки DNS;

  * если `time_total` растёт только по IPv6, а TTFB примерно одинаковый — подозрение на сеть (MTU, потери, асимметричный маршрут).




При необходимости можно прогнать наш `curl-bench.sh` отдельно с ключами `-4` и `-6`, слегка доработав скрипт (добавить параметр для передачи этих опций).

## Стреляем по конкретному бэкенду за балансировщиком

В продакшене у вас чаще всего не один сервер, а несколько бэкендов за балансировщиком. Иногда «тормозит не сайт, а конкретная нода».

### `--resolve`: фиксируем IP для домена

Если вы знаете IP конкретного сервера, можно обойти DNS и балансировщик:
    
    
    curl -s -o /dev/null -w @curl-format.txt \
      --resolve site.com:443:203.0.113.10 \
      https://site.com/
    

Что делает `--resolve`:

  * говорит `curl`: «для `site.com:443` используй IP `203.0.113.10`, не трогай DNS»;

  * при этом в HTTP‑запросе остаётся Host: `site.com`, то есть виртуальный хост на бэкенде работает как обычно.




Так можно сравнить тайминги разных бэкендов:
    
    
    curl -s -o /dev/null -w @curl-format.txt \
      --resolve site.com:443:203.0.113.10 https://site.com/
    
    curl -s -o /dev/null -w @curl-format.txt \
      --resolve site.com:443:203.0.113.11 https://site.com/
    

Если один сервер стабильно медленнее другого по TTFB — есть повод искать проблему именно на нём (нагрузка, медленный диск, локальные логи, кривые кэш‑правила и т.п.).

### 

### `--connect-to`: перекидываем трафик на другой хост/порт

Иногда нужен более гибкий вариант: изменить и хост, и порт назначения. Для этого есть `--connect-to`:
    
    
    curl -s -o /dev/null -w @curl-format.txt \
      --connect-to site.com:443:10.0.0.5:8443 \
      https://site.com/
    

Смысл такой:

  * логически мы идём на `https://site.com:443`;

  * физически `curl` соединяется с `10.0.0.5:8443`;

  * в заголовке Host остаётся `site.com`.




Этим удобно ходить на тестовые бэкенды, нестандартные порты, локальные dev‑окружения, не меняя URL и не трогая DNS.

## HTTP/1.1, HTTP/2, HTTP/3: влияние протокола

Если ваш `curl` собран с поддержкой HTTP/2 и HTTP/3, можно посмотреть, как меняется время ответа при разных версиях протокола.

Примеры:
    
    
    # Force HTTP/1.1
    curl --http1.1 -s -o /dev/null -w @curl-format.txt https://site.com/
    
    # HTTP/2
    curl --http2 -s -o /dev/null -w @curl-format.txt https://site.com/
    
    # HTTP/3 (if the server and curl support)
    curl --http3 -s -o /dev/null -w @curl-format.txt https://site.com/
    

Что здесь важно понимать начинающему админу:

  * для одной простой страницы без кучи ресурсов разница может быть почти незаметна;

  * преимущество HTTP/2/3 особенно видно, когда много параллельных запросов (картинки, CSS, JS);

  * иногда HTTPS + HTTP/2 на старом железе или под специфичным прокси может, наоборот, ухудшать ситуацию.




Задача `curl` в этом контексте — дать вам «голое» время ответа по каждому протоколу.

## Редиректы: декомпозируем цепочку

В первой главе мы включали опцию `-L`, чтобы `curl` следовал редиректам. Но как посмотреть, где именно в цепочке мы теряем время? Одна из простых тактик — пройти цепочку по шагам вручную.

### 

### Сначала смотрим только заголовки (`-I`)
    
    
    curl -I https://site.com/
    

Вы увидите что‑то вроде:
    
    
    HTTP/2 301
    location: https://www.site.com/
    ...
    

Дальше можно пройтись по каждому шагу отдельно:
    
    
    curl -s -o /dev/null -w @curl-format.txt https://site.com/
    
    curl -s -o /dev/null -w @curl-format.txt https://www.site.com/
    

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

### 

### Автоматическое следование (`-L`) с отображением кода ответа

Можно добавить в формат вывода переменные `%{url_effective}` и `%{http_code}`, чтобы после всех редиректов увидеть итоговый URL и статус:
    
    
    curl -s -L -o /dev/null -w 'code: %{http_code}\nurl: %{url_effective}\n' https://site.com/
    

Это не даст тайминги по каждому отдельному шагу, но покажет итоговую комбинацию «куда пришли» + «чем закончилось».

В более сложных сценариях (много редиректов, разные домены) обычно всё же удобнее пройтись вручную по каждому звену.

## Измеряем разные этапы отдельно: TTFB vs размер ответа

Иногда важно разделить:

  * _время, пока сервер думает_ ;

  * _время, пока контент докачивается_.




Напомним ключевые моменты:

  * `time_starttransfer` — примерно TTFB, время до первого байта;

  * `time_total` — полное время до получения всего тела.




Если вы хотите прямо видеть разницу, добавьте в формат строку вроде:
    
    
    Server processing (TTFB): %{time_starttransfer}s
    Download tail:            %{time_total} - %{time_starttransfer}s
    

К сожалению, `curl` не умеет вычитать переменные прямо в формате, но вы можете посчитать это в своей голове или в небольшом скрипте, где вызываете `curl` и затем через `awk` считаете разность.

Пример грубого скрипта:
    
    
    curl -s -o /dev/null -w '%{time_starttransfer} %{time_total}\n' https://site.com/ \
      | awk '{ttfb=$1; total=$2; tail=total-ttfb; \
             printf "TTFB: %.3fs\nTail: %.3fs\n", ttfb, tail}'
    

Если `TTFB` большой — смотрим в сторону бэкенда и базы. Если `Tail` странно большой — возможно, ответ очень тяжёлый или где‑то по пути проблемы с пропускной способностью.

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

Иногда число секунд — это ещё не всё. Хотите понять, попали ли вы в кэш, в какой датацентр, на какой ноде оказались? Смотрим заголовки.

Пример:
    
    
    curl -s -D - -o /dev/null https://site.com/
    

Опция `-D -` выводит заголовки ответа. Часто там есть служебные поля:

  * `X-Cache: HIT/MISS` — попали ли мы в кэш;

  * `X-Served-By`, `X-Backend` — имя бэкенда или датацентра;

  * `Server-Timing` — иногда там лежат внутренние тайминги приложения.




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

## Мини‑итог 

Во второй главе мы:

  * превратили `curl` из разового выстрела в **мини‑бенчмарк** с несколькими запросами, средним и p95;

  * научились сравнивать **IPv4 и IPv6** ;

  * научились стрелять в **конкретный бэкенд за балансировщиком** с помощью `--resolve` и `--connect-to`;

  * посмотрели, как влияет версия протокола (HTTP/1.1, HTTP/2, HTTP/3);

  * научились разбирать **цепочки редиректов** и отделять TTFB от времени докачки тела ответа;

  * увидели, как вытягивать заголовки для дополнительного контекста (кэш, датацентр, нода).




На практике всё это позволяет вам не просто говорить «сайт медленный», а чётко формулировать: _«по IPv6 из Нидерландов p95 вырос до 900 мс, при этом TTFB нормальный, а хвост докачки большой — подозреваю ограничение полосы на конкретном узле или по пути»_. А пока можно уже использовать скрипт `curl-bench.sh` в повседневной жизни, чтобы перестать спорить «на глаз» и начать оперировать цифрами.

Похожие статьи

[Как посмотреть логи shutdown и restart в Windows Server](https://zomro.com/rus/blog/faq/258-kak-posmotret-logi-shutdown-i-restart-v-windows-server) [Как локации в Центральной Европе, Северной Америке и Азии влияют на торговлю на MT4/MT5](https://zomro.com/rus/blog/faq/574-data-centre-geolocation-how-central-europe-north-america-and-asia-affect-mt4mt5-trading) [Как создать пользователя и добавить домен в панель HestiaCP?](https://zomro.com/rus/blog/faq/247-kak-sozdat-polzovatelja-i-dobavit-domen-v-panel-hestiacp) [Файловый менеджер Midnight Commander](https://zomro.com/rus/blog/faq/207-fajlovyj-menedzher-midnight-commander) [Как установить Wordpress в Docker-Compose](https://zomro.com/rus/blog/faq/376-how-to-install-wordpress-in-docker-compose)

[ Выделенные серверы от €88.80/мес ](/rus/dedicated-servers)
