Learning Platform
Глоссарий Troubleshooting
Урок 16.03 · 22 мин
Начальный
curlHTTPTLSTroubleshootingAPI

curl -v, —resolve, —trace — глубокая диагностика API

curl — швейцарский нож инженера. Внешне это просто «сделать HTTP-запрос», но за этим скрывается мощный инструмент диагностики, который может рассказать всё об HTTP/HTTPS-запросе: DNS-резолв, TCP-handshake, TLS-handshake, заголовки, тело, тайминги. В этом уроке — как использовать curl на максималках для решения реальных production-проблем.

В большинстве туториалов curl показывают как «загрузить файл». Это не главное. Главное — что curl —verbose открывает все слои сетевого стека под одной командой. Когда HTTP API ведёт себя не так, curl — ваша первая остановка.


curl -v — что происходит под капотом

Флаг -v (verbose) показывает каждый шаг запроса:

curl -v https://api.github.com/users/torvalds
*   Trying 140.82.121.6:443...
* Connected to api.github.com (140.82.121.6) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=San Francisco; O=GitHub, Inc.; CN=*.github.com
*  start date: Feb 14 00:00:00 2026 GMT
*  expire date: Mar 14 23:59:59 2027 GMT
*  subjectAltName: host "api.github.com" matched cert's "*.github.com"
*  issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
*  SSL certificate verify ok.
> GET /users/torvalds HTTP/2
> Host: api.github.com
> user-agent: curl/8.4.0
> accept: */*
>
< HTTP/2 200
< server: GitHub.com
< date: Sun, 18 May 2026 14:45:00 GMT
< content-type: application/json; charset=utf-8
< x-ratelimit-limit: 60
< x-ratelimit-remaining: 59
<
{
  "login": "torvalds",
  ...
}

Понимать каждую строку:

  • * — метаинформация от curl. Это не часть HTTP-обмена, это комментарии.
  • > — что curl отправил серверу.
  • < — что сервер ответил.

Разберём по разделам.

DNS + TCP

*   Trying 140.82.121.6:443...
* Connected to api.github.com (140.82.121.6) port 443 (#0)
  • DNS уже отрезолвился (имя -> IP). Если бы failed, было бы Could not resolve host.
  • Connected — TCP handshake прошёл успешно.
  • port 443 — стандартный HTTPS.
  • (#0) — идентификатор соединения. Если бы был keep-alive с предыдущим запросом, увидели бы (#1), (#2).

TLS handshake

* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
...
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
  • ALPN — Application-Layer Protocol Negotiation. Curl предлагает h2 (HTTP/2) и http/1.1, сервер выбирает h2.
  • TLS 1.3 — используемая версия TLS. TLS 1.3 быстрее (1 RTT) и безопаснее, чем TLS 1.2.
  • TLS_AES_128_GCM_SHA256 — выбранный cipher suite.

Server certificate

* Server certificate:
*  subject: C=US; ST=California; L=San Francisco; O=GitHub, Inc.; CN=*.github.com
*  start date: Feb 14 00:00:00 2026 GMT
*  expire date: Mar 14 23:59:59 2027 GMT
*  subjectAltName: host "api.github.com" matched cert's "*.github.com"
*  issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1
*  SSL certificate verify ok.

Что важно:

  • subject CN: *.github.com — wildcard certificate для любого поддомена github.com.
  • subjectAltName: matched cert’s *.github.com — мы запрашивали api.github.com, certificate содержит *.github.com, совпадение есть.
  • start / expire date — срок действия. Если сегодня позже expire date — ошибка.
  • issuer — какой CA выдал. DigiCert — известный CA, доверенный всеми браузерами.
  • SSL certificate verify ok — всё проверено успешно.

HTTP request

> GET /users/torvalds HTTP/2
> Host: api.github.com
> user-agent: curl/8.4.0
> accept: */*
>

То, что мы отправили. Видны методы (GET), путь, версия (HTTP/2), заголовки. Пустая строка > после accept: */* — конец заголовков (нет body для GET).

HTTP response

< HTTP/2 200
< server: GitHub.com
< date: Sun, 18 May 2026 14:45:00 GMT
< content-type: application/json; charset=utf-8
< x-ratelimit-limit: 60
< x-ratelimit-remaining: 59
<
  • HTTP/2 200 — успех.
  • content-type — что в теле (JSON).
  • x-ratelimit-*- — кастомные заголовки от GitHub (60 запросов в час лимит, 59 осталось).

Внизу — собственно JSON-ответ.


Тайминги — сколько занимает каждый этап

-w опция позволяет вывести custom timing info после запроса:

curl -w '
DNS lookup:      %{time_namelookup}s
TCP connect:     %{time_connect}s
TLS handshake:   %{time_appconnect}s
First byte:      %{time_starttransfer}s
Total time:      %{time_total}s
Bytes received:  %{size_download}
HTTP code:       %{response_code}
' -o /dev/null -s https://api.github.com/users/torvalds

DNS lookup:      0.012s
TCP connect:     0.034s
TLS handshake:   0.089s
First byte:      0.231s
Total time:      0.234s
Bytes received:  1234
HTTP code:       200

Это супер ценно для performance debug:

  • DNS lookup: 12ms — быстро (из кэша).
  • TCP connect: 22ms (34-12) — handshake до сервера.
  • TLS handshake: 55ms (89-34) — TLS 1.3 один round-trip.
  • First byte: 142ms (231-89) — ждали сервер обрабатывать запрос.
  • Total: 234ms — общее.

Если на ваш API запрос идёт 2 секунды, и time_starttransfer — 1.8s, проблема в самом сервере, не в сети. Если time_appconnect — 1.5s, проблема в TLS handshake (медленные сертификаты, проблема с DNS-резолвом OCSP).

# Удобный alias для timing:
alias curl-timing="curl -w '\ndns:%{time_namelookup}s\ntcp:%{time_connect}s\ntls:%{time_appconnect}s\nttfb:%{time_starttransfer}s\ntotal:%{time_total}s\n' -o /dev/null -s"

curl-timing https://api.github.com

—resolve — форсируем DNS-резолв

Иногда нужно протестировать запрос на конкретный IP, не дожидаясь DNS-распространения. Или проверить, как сервер ответит, если бы DNS отдал другой IP.

# Сделать запрос на bank.example.com, но реально на 10.0.0.42:
curl --resolve bank.example.com:443:10.0.0.42 https://bank.example.com/

# Сервер на 10.0.0.42 получит запрос с Host: bank.example.com -- это будет
# выглядеть как обычный запрос к bank.example.com.

Use cases:

  • Testing new IP before DNS switch. Поднял новый сервер с новым IP, хочешь проверить, что он работает корректно — до того как переключать DNS.
  • Bypass cached DNS. Если ваш системный DNS закэширован, —resolve обходит это.
  • Testing per-region behavior. CDN отдаёт разные IP для разных регионов. —resolve позволяет тестировать конкретный edge.
  • Simulating “if DNS would return X”. Бьём конкретный CDN-узел через —resolve.
# Проверить, как ответит origin-сервер напрямую (минуя CDN):
curl --resolve example.com:443:<origin-ip> https://example.com/

# Сравнить ответ от трёх CDN nodes:
curl --resolve example.com:443:1.2.3.4 https://example.com/ -o /tmp/r1.txt
curl --resolve example.com:443:5.6.7.8 https://example.com/ -o /tmp/r2.txt
curl --resolve example.com:443:9.10.11.12 https://example.com/ -o /tmp/r3.txt
diff /tmp/r1.txt /tmp/r2.txt  # должны быть одинаковыми

—trace и —trace-ascii — очень подробный лог

--trace пишет всё, что curl делает, в файл — байт за байтом. Это нужно, когда -v недостаточно (бинарные данные, очень детальная диагностика).

# --trace-ascii пишет ASCII-readable log (бинарные байты как hex):
curl --trace-ascii /tmp/trace.txt https://api.github.com/

cat /tmp/trace.txt
== Info: Trying 140.82.121.6:443...
== Info: Connected to api.github.com (140.82.121.6) port 443 (#0)
== Info: ALPN, offering h2
== Info: ALPN, offering http/1.1
=> Send SSL data, 5 bytes (0x5)
0000: ........
=> Send SSL data, 512 bytes (0x200)
0000: .......&w.@:..tH....:....q.j.X...t..t.h..(....\..\....HC..d.;
...
=> Send header, 88 bytes (0x58)
0000: GET / HTTP/2
0010: Host: api.github.com
0026: user-agent: curl/8.4.0
003e: accept: */*
004b: 
<= Recv header, 13 bytes (0xd)
0000: HTTP/2 200
<= Recv data, 1234 bytes (0x4d2)
...

=> — отправленные данные. <= — полученные. Каждая строка показывает offset и hex/ascii содержимое.

Это слишком много для обычной диагностики, но иногда нужно (особенно для HTTP/2 binary frames, для бинарных протоколов поверх HTTP).


Тестируем плохие сертификаты

Если сертификат истёк, не доверенный, или неправильный для домена — curl ругается:

# Пример с устаревшим сертификатом:
curl -v https://expired.badssl.com/
*  SSL certificate problem: certificate has expired
* Closing connection 0
curl: (60) SSL certificate problem: certificate has expired

Принудительно игнорировать (НЕ делайте в production!):

curl -k https://expired.badssl.com/
# или
curl --insecure https://expired.badssl.com/

Это полезно для:

  • Локальный сервер с self-signed cert. Когда вы знаете, что cert не trusted, но это норма для dev.
  • Тестирование, что мы попали именно на сервер с проблемой. Игнор сертификата + посмотреть response.
# Можно проверить, какой именно сертификат сервер отдаёт:
curl -v --insecure https://expired.badssl.com/ 2>&1 | grep -E '(subject|expire|issuer)'
*  subject: CN=*.badssl.com
*  expire date: Apr 12 23:59:59 2015 GMT  # явно устаревший!
*  issuer: C=US; ...

Используйте OpenSSL для глубокого анализа сертификата:

echo | openssl s_client -showcerts -servername expired.badssl.com -connect expired.badssl.com:443 2>/dev/null | openssl x509 -inform pem -noout -text

Диагностика API-проблем

Реальный production-сценарий: «наш сервис вернул 500 после деплоя».

# 1. Базовый verbose-запрос:
curl -v https://api.example.com/v2/users/me

# Видим:
# < HTTP/2 500
# < content-type: application/json
# {"error": "internal server error"}

# 2. Проверить, не проблема ли в TLS:
curl -v --insecure https://api.example.com/v2/users/me
# Если те же 500 -- проблема не в TLS, а в сервере

# 3. Проверить, не отвалилась ли БД через health check:
curl -s https://api.example.com/healthz
# {"status": "ok"} или {"status": "db unreachable"}

# 4. Сравнить с предыдущим работающим IP (если знаете):
curl --resolve api.example.com:443:<old-ip> https://api.example.com/v2/users/me

# 5. Посмотреть response headers подробно:
curl -I https://api.example.com/v2/users/me  # только headers, HEAD-запрос

# 6. Включить trace:
curl --trace-ascii /tmp/trace.txt https://api.example.com/v2/users/me
# Открыть /tmp/trace.txt -- видно весь обмен

Иногда проблема в request body. Сравните, что вы реально отправляете:

# С --data, JSON в body:
curl -X POST -v -H 'Content-Type: application/json' \
  -d '{"username": "alex"}' \
  https://api.example.com/v2/users

# Verbose покажет, что вы реально отправили:
> POST /v2/users HTTP/2
> Host: api.example.com
> content-type: application/json
> content-length: 19
>
> {"username": "alex"}

# Если ответ 400 Bad Request с {"error": "missing field"}, видно, что body отправлен правильно
# Скорее всего сервер ожидает другое поле -- например, "user" вместо "username"

Имитация других клиентов через User-Agent

Иногда API ведут себя по-разному в зависимости от User-Agent (особенно если есть anti-bot защита):

# Default curl UA -- блокируется некоторыми сайтами:
curl https://protected-site.com/
# 403 Forbidden

# Маскируется под Chrome:
curl -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' \
  https://protected-site.com/
# 200 OK

Это полезно при scraping/диагностике защищённых API.


—data, —data-binary, —form — отправка данных

curl поддерживает несколько форматов отправки:

# Form-encoded (application/x-www-form-urlencoded):
curl -d 'username=alex&password=secret' https://api.example.com/login

# JSON:
curl -H 'Content-Type: application/json' -d '{"username":"alex"}' https://api.example.com/login

# Файл как body:
curl -d @/path/to/payload.json https://api.example.com/login

# Multipart form (file upload):
curl -F 'file=@/path/to/upload.jpg' https://api.example.com/upload

# Binary upload (не form-encode):
curl --data-binary @/path/to/file.bin -H 'Content-Type: application/octet-stream' https://api.example.com/upload

При —data curl URL-encode’ит данные. При —data-binary — нет (важно для бинарных файлов).


Headers — кастомные и стандартные

# Один header:
curl -H 'Authorization: Bearer YOUR_TOKEN' https://api.example.com/

# Несколько headers:
curl -H 'Authorization: Bearer YOUR_TOKEN' \
     -H 'Accept: application/vnd.example.v2+json' \
     -H 'X-Request-ID: 12345' \
     https://api.example.com/

# Удалить дефолтный header:
curl -H 'Accept:' https://api.example.com/
# accept: будет вообще не отправлен

# Custom Host header (для тестирования виртуальных хостов):
curl -H 'Host: alt-domain.example.com' https://shared-server.com/

Cookies — работа с авторизованными сессиями

# Сохранить cookies в файл после запроса:
curl -c /tmp/cookies.txt https://example.com/login -d 'user=...&pass=...'

# Использовать сохранённые cookies в следующих запросах:
curl -b /tmp/cookies.txt https://example.com/protected

# Объединить -- сохранить новые и использовать существующие:
curl -b /tmp/cookies.txt -c /tmp/cookies.txt https://example.com/page

# Передать конкретный cookie в команде:
curl -b 'session=abc123' https://example.com/

Это бесценно для тестирования login flow:

# Шаг 1: login и сохранить session cookie
curl -c /tmp/c.txt -d 'username=test&password=test' https://example.com/login

# Шаг 2: посмотреть, что в cookies
cat /tmp/c.txt
# # Netscape HTTP Cookie File
# # ...
# example.com   FALSE   /   FALSE   0   session   abc123def

# Шаг 3: использовать session для следующих запросов
curl -b /tmp/c.txt https://example.com/api/profile

Попробуй сам

Соберите полный pipeline диагностики API:

# 1. Базовый verbose:
curl -v https://api.github.com/users/torvalds 2>&1 | head -40

# 2. Тайминги:
curl -w '\ntotal:%{time_total}s, dns:%{time_namelookup}s, tcp:%{time_connect}s, tls:%{time_appconnect}s, ttfb:%{time_starttransfer}s\n' \
  -o /dev/null -s https://api.github.com/users/torvalds

# 3. Проверка только headers:
curl -I https://api.github.com/users/torvalds

# 4. Имитировать другой регион через --resolve:
# (узнать GitHub'овский IP в другом регионе через CDN-tools, например, dns-lookup из другого континента)

# 5. Подробный trace:
curl --trace-ascii /tmp/gh-trace.txt -s -o /dev/null https://api.github.com/users/torvalds
less /tmp/gh-trace.txt

# 6. Тест с разными User-Agent:
curl -A 'Mozilla/5.0 Chrome/120' https://httpbin.org/user-agent
curl -A 'curl' https://httpbin.org/user-agent

# 7. Smart workflow для production API:
HOST=api.example.com
curl --resolve $HOST:443:1.2.3.4 -v https://$HOST/healthz  # тест origin напрямую
curl -H 'X-Debug: 1' -v https://$HOST/v2/users/me  # с debug-header

# 8. Сравнить response от двух endpoints:
curl -s https://api.example.com/v1/health > /tmp/v1.json
curl -s https://api.example.com/v2/health > /tmp/v2.json
diff <(jq -S . /tmp/v1.json) <(jq -S . /tmp/v2.json)

Если у вас есть свой деплоенный API:

# Полный diagnostic workflow:
# Step 1: TLS check
curl --insecure -v https://your-api.com/ 2>&1 | grep -E '(subject|expire|issuer|verify)'

# Step 2: Latency profile
curl -w '%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n' -o /dev/null -s https://your-api.com/ 

# Step 3: Headers
curl -I https://your-api.com/

# Step 4: Detailed trace (если есть проблема)
curl --trace-ascii /tmp/trace.txt https://your-api.com/api/endpoint

# Step 5: Compare to historical/expected response
curl -s https://your-api.com/api/endpoint | jq . > /tmp/current.json
diff /tmp/baseline.json /tmp/current.json

Debugging HTTP API: curl, httpx и интерпретация статус-кодов
Проверка знанийKnowledge check
Junior спрашивает: 'У меня curl -v на свой API показывает SSL certificate problem: certificate has expired, при том что сертификат я обновил вчера через Let''s Encrypt. В админке всё новое. Что не так?'
ОтветAnswer
Несколько возможных причин, и они все наблюдаемы через curl. Первая -- кэширование на промежуточных уровнях. Если ваш API за CDN (Cloudflare, AWS CloudFront), CDN кэширует TLS-сертификат на каком-то слое. Обновили origin -- CDN ещё на старом. Чтобы проверить: curl --resolve your-api.com:443:<origin-ip> -v https://your-api.com/ -- если на origin сертификат новый, проблема в CDN. Решение: purge CDN cache или refresh certificate в CDN settings. Вторая -- web-сервер не подцепил новый сертификат. Let's Encrypt certbot обновил файлы, но NGINX/Apache не перезагружен и держит в памяти старые. Проверить: ls -la /etc/letsencrypt/live/your-domain/ -- посмотреть дату изменения cert.pem. Сравнить с тем, что отдаёт curl. Если файл свежий, а curl отдаёт старый -- nginx reload (systemctl reload nginx). Третья -- неправильный VirtualHost / SNI. У вашего сервера несколько доменов и certificates. curl шлёт SNI с your-api.com, а сервер настроен на default-server со старым certificate. Проверить через openssl: echo | openssl s_client -servername your-api.com -connect your-api.com:443 2>/dev/null | openssl x509 -noout -dates -subject. Видно, какой именно cert отдаётся для вашего SNI. Четвёртая -- ваш локальный CA-bundle устарел. Маловероятно, но возможно: curl/openssl на старой системе не знают новый Let's Encrypt root CA. Решение: brew update curl на macOS, apt update ca-certificates на Linux. Пятая -- system clock skew. Если время на сервере или клиенте сильно отстаёт, TLS validation думает, что cert ещё не валиден или уже expired. Проверить через date. Workflow: curl --insecure -v https://your-api.com/ -- посмотреть, что cert subject / expire date именно показывают. Это даёт ground truth -- что сервер реально отдаёт сейчас.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 6. В curl -v output что значат символы '*', '>' и '<' в начале строк?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5