Skip to content

Traefik TCP and UDP routers — SNI routing, raw streams, and what you give up

Use Traefik beyond HTTP — TCP routers with SNI matching, UDP entrypoints for DNS or game traffic, and the trade-offs you accept once you leave HTTP.

Traefik is best known as an HTTP reverse proxy but its TCP and UDP routers turn it into a competent layer-4 proxy too. The same entrypoint that serves HTTPS on 443 can also route TLS-wrapped traffic to a non-HTTP backend (MQTT-over-TLS, gRPC, MySQL with TLS) by matching on SNI. UDP routers handle DNS, QUIC where it is not yet an HTTP/3 entrypoint, and game-style traffic. This article covers the configuration shape and the operational trade-offs you accept by leaving the HTTP world behind.

How to verify

# TCP router listeners
curl -s http://127.0.0.1:8082/api/tcp/routers   | jq '.[] | {name, rule, service, status}'
# A TLS-passthrough TCP connection should land on the right backend by SNI
openssl s_client -connect db.example.com:443 -servername db.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject
# UDP router listeners
curl -s http://127.0.0.1:8082/api/udp/routers   | jq .
# Send a DNS query through a UDP entrypoint
dig @udp.example.com -p 53 example.com +short

What’s happening

A TCP router lives next to HTTP routers but has a smaller match surface: SNI hostname (HostSNI(db.example.com)), the client’s ALPN protocol, or the client IP. Critically, only SNI is available before TLS termination — if Traefik terminates TLS itself the rule can match on the host, but if it forwards the TLS handshake intact (passthrough mode) it can only see the SNI. There is no path matching at TCP because there is no HTTP path.

TCP middlewares are sparse: ipAllowList and inFlightConn are the production set. There is no rate limit, no auth, no retry — those would require inspecting the stream content which Traefik does not do.

UDP routers are stateless by definition; there is no SNI. Routing is by entrypoint only — every UDP packet on an entrypoint goes to the configured service. The use cases are narrow: DNS load balancing, QUIC traffic that you want to terminate at Traefik (rare; usually that lives directly in the app), and game/voice traffic.

The big trade-off: leaving HTTP means losing every HTTP middleware. No headers, no rewrites, no auth, no access log (TCP has a coarser log line, UDP has none useful). If you need any of that, terminate TLS at Traefik, run HTTP underneath, and route at L7.

The procedure

  1. TCP passthrough with SNI routing. The same entrypoint handles HTTPS and TLS-wrapped MQTT — Traefik chooses based on SNI before forwarding the stream.

    tcp:
      routers:
        mqtt:
          rule: "HostSNI(`mqtt.example.com`)"
          entryPoints: [websecure]
          service: mqtt-broker
          tls:
            passthrough: true   # do not terminate TLS here
      services:
        mqtt-broker:
          loadBalancer:
            servers:
              - address: "10.0.2.10:8883"
              - address: "10.0.2.11:8883"
  2. TCP terminate-and-forward. Traefik terminates TLS using its own resolver, then forwards a plain TCP stream to the backend — useful when the backend doesn’t speak TLS or runs a different cert.

    tcp:
      routers:
        postgres:
          rule: "HostSNI(`pg.example.com`)"
          entryPoints: [websecure]
          service: postgres
          tls:
            certResolver: letsencrypt
      services:
        postgres:
          loadBalancer:
            servers:
              - address: "10.0.3.10:5432"
  3. A catch-all TCP router on a dedicated entrypoint — useful for opaque protocols on a non-TLS port.

    entryPoints:
      redis-edge:
        address: ":6379"
    tcp:
      routers:
        redis-all:
          rule: "HostSNI(`*`)"
          entryPoints: [redis-edge]
          service: redis-backends
      services:
        redis-backends:
          loadBalancer:
            servers:
              - address: "10.0.4.10:6379"
              - address: "10.0.4.11:6379"

    HostSNI(*) is required even when no SNI is sent — it acts as a wildcard.

  4. UDP routing. Each UDP entrypoint can have exactly one router rule because there is no header to match on; routing is implicit by port.

    entryPoints:
      dns:
        address: ":53/udp"
    udp:
      routers:
        dns:
          entryPoints: [dns]
          service: dns-upstream
      services:
        dns-upstream:
          loadBalancer:
            servers:
              - address: "10.0.5.10:53"
              - address: "10.0.5.11:53"
  5. Add an IP allow-list to a TCP router for office-only access:

    tcp:
      middlewares:
        office-only:
          ipAllowList:
            sourceRange:
              - "203.0.113.0/24"
      routers:
        postgres:
          rule: "HostSNI(`pg.example.com`)"
          entryPoints: [websecure]
          service: postgres
          middlewares: [office-only]
          tls:
            certResolver: letsencrypt

Operational notes

  • TCP passthrough mode means Traefik never sees the cert or the certificate verification result — your monitoring loses TLS-level signal. Terminate at Traefik when you can.
  • SNI-based routing requires the client to send SNI. Some older clients and most non-browser tooling (psql, mysql) need explicit configuration to send it.
  • UDP “load balancing” is connectionless. There is no session affinity; consecutive packets from the same client can land on different backends. For DNS that’s fine; for anything stateful (QUIC sessions, voice) it usually isn’t.
  • TCP access logs are minimal — source IP, target service, bytes in/out. No per-request information because TCP has no requests.
  • Mixing TCP and HTTP routers on the same entrypoint requires that the TCP router match on something the HTTP router cannot, otherwise Traefik picks one and the other never fires. SNI-based selection is the clean separation.

Layer-4 routing pulls a Traefik install into territory where most of the HTTP-tier observability and security goes quiet. In the engagements we run, TCP/UDP routes are documented separately from the L7 surface and monitored with backend-side health, because Traefik’s own signal is intentionally coarse here. That model is part of our managed operations playbook. For HTTP middleware patterns that you lose at L4, see traefik-routers-services-middlewares.