salfer@blog:~$ ▊
26.10.2026 · сетиnginx

Один порт — много секретов: SNI-демультиплексор на nginx

Классическая задача: на сервере один внешний адрес, а за ним — и публичный сайт, и то, что должно быть видно только своим. Открывать по порту на каждый сервис — получается «новогодний календарь» для сканера. Решение, которым я давно пользуюсь: stream-блок nginx с раскладкой по SNI ещё до установления TLS.

Схема

stream {
    map $ssl_preread_server_name $backend {
        site.example.com   127.0.0.1:8443;   # сайт: терминируем
        svc1.example.com   127.0.0.1:9101;   # сервис: passthrough
        default            127.0.0.1:8443;
    }
    server {
        listen 443;
        proxy_pass $backend;
        ssl_preread on;
    }
}

Когда passthrough лучше терминирования

Если сервис за раскладкой сам умеет TLS и сам отвечает за свой сертификат — не трогай его трафик. Passthrough сохраняет оригинальное рукопожатие: клиент видит чужой сертификат ровно такой, каким его отдал сервис. Любая твоя подмена — заметная аномалия для того, кто смотрит.

Терминирование оправдано, когда нужно смотреть внутрь (логи, модификация заголовков) или делить один сертификат между бэкендами. Но за каждое «посмотреть внутрь» платишь тем, что теперь и ты отвечаешь за этот трафик.

Грабли

Первое: ssl_preread читает только первый ClientHello. Если клиент шлёт GREASE или разбитые пакеты — nginx всё равно справится, но старые сборки падали на этом, обновляйтесь. Второе: не забудьте proxy_protocol согласовать на обоих концах, если пробрасываете адрес клиента — иначе бэкенд увидит 127.0.0.1 во всех логах, и вы потеряете возможность банить по IP.

И главное правило: сканер, постучавший на 443 без SNI, должен получать ровно то же, что получает на обычном сайте. Тогда порт выглядит скучно. Скучный порт — живой порт.

← к списку записей