Перейти к содержимому

curl --resolve и SNI: проверка виртуального хоста без /etc/hosts

Когда нужно проверить виртуальный хост на конкретном IP, но править /etc/hosts нет желания — ни из-за прав, ни из-за конфликтов с другими сервисами — curl --resolve решает обе задачи: подменяет DNS-запись и корректно отправляет SNI в TLS-хендшейке.

Проблема: виртуальный хост без правки /etc/hosts

На одном IP может висеть десяток виртуальных хостов, и сервер выбирает нужный по Host-заголовке (HTTP/1.1) и по SNI (TLS). Если в /etc/hosts нет записи, curl сначала попробует резолвить имя через DNS — и получит не тот IP, или вовсе не получит ответ.

Правка /etc/hosts работает, но требует sudo, засоряет файл и может сломать другие сервисы, которые завязаны на ту же запись.

curl –resolve: синтаксис и пример

Флаг --resolve перехватывает разрешение имени на уровне curl и подменяет его:

curl --resolve HOST:PORT:ADDR URL
КомпонентЗначение
HOSTИмя виртуального хоста
PORTПорт (обычно 443 для HTTPS)
ADDRЦелевой IP-адрес

Пример:

curl --resolve example.com:443:203.0.113.50 https://example.com/health

curl отправит запрос на 203.0.113.50:443, но в HTTP-заголовке Host будет example.com.

SNI в связке с –resolve

Примечание

--resolve не меняет имя, которое curl отправляет в SNI. SNI берётся из URL.

Это ключевой момент. Если в URL указано https://example.com, curl в TLS ClientHello отправит example.com как SNI — даже если IP подменён через --resolve. Сервер по SNI выберет правильный сертификат и виртуальный хост.

Проверить, что SNI действительно отправляется, можно с помощью openssl:

openssl s_client -connect 203.0.113.50:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer

Если SNI пустой или неверный, сервер вернёт дефолтный сертификат — и curl выдаст ошибку SSL: certificate subject name does not match.

Практический пример проверки виртуального хоста

Допустим, на сервере 198.51.100.10 крутится несколько виртуальных хостов, и нужно проверить app.local без правки /etc/hosts:

curl -v --resolve app.local:443:198.51.100.10 https://app.local/api/status

В выводе -v видно:

  • Connected to 198.51.100.10 (198.51.100.10) port 443 — подключение на нужный IP.
  • > Host: app.local — правильный Host-заголовок.
  • TLS SNI extension: "app.local" — SNI отправлен корректно.

Для массовой проверки нескольких хостов на одном IP:

for host in app.local api.local admin.local; do
  echo "=== $host ==="
  curl --resolve "$host:443:198.51.100.10" "https://$host/health" -s -o /dev/null -w "%{http_code}\n"
done
Предупреждение

--resolve действует только для текущего процесса curl. После завершения команды подмена исчезает — файл /etc/hosts остаётся нетронутым.

Если нужно, чтобы подмена работала для всех инструментов в терминале, а не только для curl, тогда nsupdate или локальный DNS-резолвер (dnsmasq, stubby) — более подходящий вариант. Но для разовой проверки виртуального хоста --resolve быстрее и безопаснее.