- Caddy allow: 127.0.0.0/8 → 127.0.0.1/32 (only loopback peer is HAProxy).
- haproxy.cfg: rewrite v6only comment to describe what it actually does
(suppresses v4-mapped accept, preventing conflict with the v4 bind),
not the symptom.
- docker-compose.yml: trim the 8-line haproxy comment to 3 lines and
defer the rationale to README. Add one-line note explaining why web
uses host port 8080 (HAProxy owns :80).
- README: condense the "Why network_mode: host" subsection. Spell out
trade-offs as a list: own-the-host-ports, Linux-only (Docker Desktop
doesn't make this layout reachable), userns-remap incompatibility.
Note that mtg-config.toml stays as-is because mtg/web remain on the
compose bridge.
sni-router: switch HAProxy to host networking for real client IPs
Bridge ingress (Docker's docker-proxy userland forwarder, Podman's
slirp4netns/pasta) rewrites the source IP of inbound connections on a
published port to the bridge gateway address. HAProxy then stamps that
gateway address into the PROXY v2 header it forwards to mtg and Caddy,
so neither backend ever sees a real client IP.
Move HAProxy into the host netns (network_mode: host) so it binds
:443/:80 directly with no NAT in the path. mtg and Caddy stay on the
compose bridge and are published on 127.0.0.1 only; HAProxy reaches
them via host loopback and PROXY v2 carries the real client IP (v4 or
v6) end-to-end.
Also accept IPv6 clients explicitly on the HAProxy frontends — `bind
*:443` is IPv4-only and missed v6 clients on hosts where the previous
example happened to "work" only because of dual-stack quirks.
Add 127.0.0.0/8 to Caddy's PROXY allow-list to cover the new loopback
hop from HAProxy. README gains a short subsection explaining the
host-mode choice and its trade-off (HAProxy occupies host :443/:80).
Diagnosed and tested by @bam80 on Fedora + Docker 29. Fixes #498.
Pass real client IPs through with PROXY protocol v2
Without this, mtg and Caddy see HAProxy's container IP for every
connection, which breaks meaningful logging, abuse handling, and any
IP-based blocklist logic. HAProxy sends a PROXY protocol v2 header on
its TCP backends; mtg enables proxy-protocol-listener, and Caddy wraps
:8443 with a proxy_protocol listener before tls.
The :80 path (ACME HTTP-01 passthrough) is unchanged — client IP there
is not useful and HAProxy's http mode already adds X-Forwarded-For if
anyone wants it.
Requested in https://github.com/9seconds/mtg/pull/462 review.
Add an ACL that routes /.well-known/acme-challenge/ requests on :80
to Caddy instead of redirecting to HTTPS, so Let's Encrypt certificate
issuance works out of the box.
Also simplify Caddyfile to use Caddy's http_port/https_port directives.
Add docker-compose example with HAProxy SNI router
Turnkey deployment: HAProxy on :443 peeks at the TLS SNI and routes
Telegram clients to mtg while forwarding everything else (including DPI
probes) to a real Caddy web server with automatic HTTPS.
This is the setup recommended in BEST_PRACTICES.md, packaged so that
operators can clone and run it with minimal configuration.
Refs: #458