Files
admin/.wiki/concepts/traefik-tcp-passthrough-vs-starttls.md

4.8 KiB
Raw Blame History

title, type, tags, sources, updated
title type tags sources updated
Traefik TCP passthrough vs STARTTLS-protocols concept
traefik
tls
tcp
sni
postgres
mariadb
mongo
redis
gotcha
../sources/vds-kzntsv-bootstrap-2026-05-20.md
2026-05-20

Traefik TCP passthrough не работает с STARTTLS

Симптом

Traefik TCP router с HostSNI(<hostname>) + tls.passthrough=true. Mongo / Redis (TLS-from-start) — TLS handshake проходит, виден правильный cert. Postgres / MariaDB — TLS connection hangs / timeout, никакого handshake'а не происходит.

Probe openssl s_client -connect postgres.vds.kzntsv.site:5432 -servername postgres.vds.kzntsv.site -starttls postgres → timeout 10s.

Root cause

tls.passthrough=true означает: traefik не терминирует TLS, а маршрутизирует TCP-connection raw. Для маршрутизации по HostSNI traefik читает SNI из TLS ClientHello — первого TLS-сообщения, отправляемого клиентом сразу после TCP handshake.

Mongo / Redis (TLS-from-start) — клиент шлёт TLS ClientHello как первое сообщение. SNI там присутствует. Traefik читает, маршрутизирует, остаток TCP forwarding'ом. Работает.

Postgres / MariaDB / MySQL — используют STARTTLS pattern:

  1. Клиент после TCP-handshake шлёт SSLRequest (plaintext, специфичный для протокола).
  2. Сервер отвечает 'S' (готов на TLS).
  3. Только после этого клиент начинает TLS ClientHello.

Первые байты от клиента — plaintext protocol bytes, не TLS ClientHello. Traefik ищет SNI в TLS ClientHello, не находит → не может смаршрутизировать → connection hangs (висит на чтении от клиента).

Решение — Raw TCP forward + HostSNI(*)

labels:
  - traefik.enable=true
  - "traefik.tcp.routers.postgres.rule=HostSNI(`*`)"
  - traefik.tcp.routers.postgres.entrypoints=postgres
  - traefik.tcp.routers.postgres.service=postgres
  - traefik.tcp.services.postgres.loadbalancer.server.port=5432
  # NO tls.* — traefik forwards raw TCP без inspect

Каждая DB — на своей dedicated entrypoint port (postgres:5432, mariadb:3306, mongo:27017, redis:6379). Routing — по entrypoint, не по SNI. Traefik просто forwards bytes к backend без чтения TLS ClientHello. STARTTLS protocols договариваются о TLS напрямую с DB-контейнером.

HostSNI(\`)— единственное значение для TCP router'а **без**tls.` секции (traefik требует какое-то правило, wildcard catch-all уместен здесь).

Trade-off

  • ✔ Работает для всех 4 protocols (STARTTLS + TLS-from-start).
  • ✔ Uniform config, не нужно ветвить compose под тип protocol'а.
  • ✗ Каждая DB требует своего entrypoint:port в traefik static config. 4 DB → 4 entrypoints. Если хотим много инстансов одного движка — теряем muxing на один port через SNI.
  • ✗ Traefik в этом случае не видит контент трафика — только TCP-bytes мимо. Невозможно сделать middleware (rate limit, IP allowlist на уровне traefik) — это нужно делать на DB side.

Альтернативный путь (отвергнут)

Traefik TLS termination (без passthrough) + SNI muxing на 443 entrypoint. Не работает для PG/MariaDB по той же причине — STARTTLS встроен в protocol stack, и traefik терминирующий TLS отдаёт plaintext бэкенду, который ждёт SSLRequest и предлагает свой TLS handshake. Двойная TLS обёртка вокруг STARTTLS — broken.

LE-cert provisioning при passthrough TCP — на DB side, не traefik. Сейчас self-signed; follow-up — lego sidecar extracting из traefik acme.json.

Где применено

Все 4 DBs на vds-kzntsv: postgres:5432, mariadb:3306, mongo:27017, redis:6379. Traefik v2.11 LTS. См. также db-tls-self-signed-via-traefik-raw-tcp — связанный паттерн self-signed cert provisioning'а.

Ссылки