Проксирование на уровне HTTP закрывает большинство задач, но иногда приложению нужен «сырой» TCP или UDP. Типичные примеры — PostgreSQL и MySQL, DNS, MQTT, syslog, игровые серверы и старые протоколы без HTTP-обёртки. Модуль stream в Nginx работает именно с потоком байтов: он не разбирает заголовки и не маршрутизирует по URL, зато даёт балансировку, таймауты, ограничения и TLS на транспортном уровне. Разберём рабочие конфигурации и диагностику.
Когда stream уместен, а когда нет
Stream полезен, когда нужно спрятать несколько backend-серверов за одним адресом, добавить отказоустойчивость или завершать TLS перед приложением, которое само не поддерживает TLS. Он не подходит для маршрутизации по пути, кэширования ответов, проверки заголовков и переписывания URL — это задачи http-модуля. Если база умеет репликацию, а клиентские драйверы поддерживают несколько хостов, прокси может оказаться лишним звеном; но для старых клиентов он спасает.
- TCP: PostgreSQL, MySQL, Redis, MQTT, SMTP, игровые серверы.
- UDP: DNS, syslog, SNMP, потоковое видео, VoIP.
- TLS passthrough: маршрутизация по SNI без расшифровки трафика.
Проверка модуля и место в конфигурации
Сначала убедитесь, что модуль собран. В Debian и Ubuntu он часто вынесен в пакет libnginx-mod-stream, в RHEL-семействе — в nginx-mod-stream. Директива stream допустима только в главном контексте, рядом с http, но не внутри него: это частая ошибка при копировании примеров из документации.
nginx -V 2>&1 | tr ' ' '\n' | grep -i stream
nginx -t
Чтобы не раздувать основной файл, вынесите виртуальные серверы в отдельный каталог и подключите его одним include. Проверка синтаксиса выполняется до перезагрузки, поэтому опечатка не уронит рабочий сервис.
# /etc/nginx/nginx.conf
stream {
include /etc/nginx/stream.d/*.conf;
}
TCP: PostgreSQL за прокси
Для базы важны короткий таймаут подключения и длинный таймаут простоя: транзакция может думать минутами, и обрыв по таймауту простоя недопустим. Директива backup оставляет сервер в резерве, а max_fails и fail_timeout включают пассивную проверку доступности.
upstream postgres_backend {
server 10.0.10.11:5432 max_fails=3 fail_timeout=10s;
server 10.0.10.12:5432 backup;
}
server {
listen 5432;
proxy_pass postgres_backend;
proxy_connect_timeout 3s;
proxy_timeout 1h;
proxy_buffer_size 16k;
}
Пассивная проверка означает, что Nginx узнаёт о проблеме только после неудачного соединения. Активные health checks в открытой версии отсутствуют — это возможность коммерческой подписки. Не включайте повтор запроса к другому серверу без понимания семантики: возможно двойное выполнение.
UDP: DNS и другие датаграммы
В UDP нет соединения, поэтому сессия живёт по таймауту, а число ожидаемых ответов задаётся явно. Для DNS обычно достаточно одного ответа на запрос; если поставить слишком большое значение, сессия будет висеть до истечения proxy_timeout.
upstream dns_backend {
server 10.0.20.21:53;
server 10.0.20.22:53;
}
server {
listen 53 udp;
proxy_pass dns_backend;
proxy_responses 1;
proxy_timeout 2s;
}
Для DNS over TCP добавьте отдельный server с listen 53; — это разные транспорты, и один блок их не покрывает. Проверяйте, что upstream отвечает на запросы с адреса прокси, а не только с localhost, иначе после переключения трафика клиенты получат отказ.
Адрес клиента и PROXY protocol
После прокси backend видит адрес Nginx, а не пользователя. Если приложение пишет аудит или ограничивает доступ по IP, включите PROXY protocol, но только когда backend его поддерживает. Иначе соединение развалится на первом пакете.
server {
listen 5432;
proxy_pass postgres_backend;
proxy_protocol on;
}
В http-части принимающая сторона настраивается через set_real_ip_from и real_ip_header proxy_protocol. Не доверяйте заголовку со всех адресов: поддельный PROXY-префикс исказит источник в логах и проверках доступа.
TLS passthrough и маршрутизация по SNI
Иногда сертификаты должны оставаться на backend, а Nginx — только распределять соединения по имени. Тогда используется ssl_preread: прокси читает ClientHello, но не расшифровывает трафик и не видит запросы.
stream {
upstream web_app { server 10.0.30.11:8443; }
upstream api_app { server 10.0.30.12:8443; }
map $ssl_preread_server_name $backend {
app.example.com web_app;
api.example.com api_app;
default web_app;
}
server {
listen 443;
ssl_preread on;
proxy_pass $backend;
}
}
Учтите ограничения: без расшифровки недоступны HTTP-логи, WAF и проверка заголовков, а SNI не является механизмом аутентификации. Клиенты без SNI попадут в default, поэтому он должен вести на корректный backend. Если нужен именно TLS-терминатор, используйте ssl_certificate внутри stream-сервера, но тогда сертификаты придётся обновлять на прокси.
Логи, лимиты и диагностика
Формат лога в stream отличается от http: здесь есть протокол, байты в обе стороны и время сессии, но нет URL и кода ответа. Логируйте upstream_addr — без него сложно понять, кто именно тормозил.
log_format stream_main '$remote_addr:$remote_port $protocol $status '
'$bytes_sent $bytes_received $session_time '
'$upstream_addr';
access_log /var/log/nginx/stream.log stream_main;
Для защиты от злоупотреблений работают limit_conn_zone и limit_conn, а также proxy_download_rate и proxy_upload_rate. Ограничение скорости легко ломает легитимные бэкапы и репликацию, поэтому вводите его точечно для конкретного server, а не глобально.
ss -lntup | grep -E ':(5432|53|443)\b'
journalctl -u nginx --since '15 min ago' --no-pager
tcpdump -ni any 'port 5432' -c 20
Если соединение устанавливается, но данных нет, смотрите таймауты и MTU. Симптом «мелкое работает, крупное зависает» часто указывает на PMTU и блокировку ICMP, а не на Nginx. Для проверки слушающих сокетов и очередей используйте ss, для журналов — journalctl, для пакетов — tcpdump на своём интерфейсе.
Чек-лист перед продакшеном
- nginx -t проходит, конфигурация включена в главный контекст.
- Таймауты соответствуют поведению приложения, а не взяты наугад.
- PROXY protocol включён только там, где backend его понимает.
- В логах есть upstream и время сессии.
- Настроен мониторинг ошибок и количества соединений.
- Отдельно отслеживаются задержки и повторные подключения.
- Изменения проверены на стенде с реальным клиентом.
Итог
Stream превращает Nginx в аккуратный L4-балансировщик: несколько директив закрывают TCP и UDP, TLS passthrough и ограничения. Главное — не включать функции вслепую: PROXY protocol ломает неподготовленный backend, а агрессивные таймауты рвут долгие транзакции. Начните с одного сервиса и метрик.