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 и интерпретация статус-кодов