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

Индекс LLMS: [llms.txt](/llms.txt)

---

Когда нужно проверить виртуальный хост на конкретном 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-адрес |

Пример:

```bash
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

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

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

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

```bash
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`:

```bash
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:

```bash
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
```

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

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