# HAProxy SNI router — Layer 4 (TCP mode) # # Inspects the SNI in the TLS ClientHello and routes traffic: # - SNI matching the mtg secret domain -> mtg (FakeTLS / MTProto) # - Everything else -> real web backend (Caddy) # # Because routing happens before TLS termination, each backend sees the # raw ClientHello and handles TLS itself. The real web backend therefore # presents a genuine certificate to any probe or browser. global log stdout format raw local0 info maxconn 4096 defaults log global mode tcp option tcplog timeout connect 5s timeout client 60s timeout server 60s # --- HTTP :80 — ACME challenges + redirect ----------------------------------- frontend http # Explicit v4 + v6 binds so IPv6 clients are accepted regardless of # the host's net.ipv6.bindv6only sysctl. bind :80,[::]:80 mode http # Let Caddy answer ACME HTTP-01 challenges for Let's Encrypt. acl is_acme path_beg /.well-known/acme-challenge/ use_backend web_acme if is_acme http-request redirect scheme https code 301 # --- TLS :443 — SNI-based routing ------------------------------------------- frontend tls bind :443,[::]:443 tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } # Route Telegram clients to mtg. The domain is read from the $DOMAIN # environment variable (forwarded by docker-compose), so it stays in # sync with Caddy and there is no per-deploy edit to this file. use_backend mtg if { req_ssl_sni -i "${DOMAIN}" } default_backend web # Backends reach mtg and web on host loopback — they publish to 127.0.0.1 # (see docker-compose.yml), and HAProxy runs in the host netns # (network_mode: host). PROXY v2 still carries the real client address # (v4 or v6) end-to-end, independent of the loopback transport. backend mtg # send-proxy-v2 prepends a PROXY protocol v2 header so mtg sees the # real client IP instead of HAProxy's. mtg must have # `proxy-protocol-listener = true` in its config. server mtg 127.0.0.1:3128 send-proxy-v2 backend web # send-proxy-v2 prepends a PROXY protocol v2 header so Caddy logs the # real client IP instead of HAProxy's. Caddy must enable the # proxy_protocol listener wrapper on :8443 (see Caddyfile). server web 127.0.0.1:8443 send-proxy-v2 backend web_acme mode http server web 127.0.0.1:8080