UDP routing in Traefik often surprises people because it seems so simple, but the devil is absolutely in the details of how Traefik handles UDP packets versus its more common HTTP counterpart.
Let’s see Traefik routing a UDP service. Imagine you have a game server or a DNS server that speaks UDP and you want Traefik to act as its entry point, distributing traffic to multiple instances.
# traefik.yaml
api:
dashboard: true
entryPoints:
web-udp:
address: ":53" # Example: DNS server on port 53
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
# Define the UDP router
routers:
dns-router:
entryPoints:
- "web-udp"
rule: "HostSNI(`*`)" # For UDP, HostSNI is often used as a catch-all or for specific TLS-based SNI if applicable, though less common for pure UDP. For simple UDP, a rule that matches all traffic on the entrypoint is typical.
service: "dns-service"
# Define the UDP service
services:
dns-service:
loadBalancer:
servers:
- address: "192.168.1.100:53"
- address: "192.168.1.101:53"
# UDP specific configuration
udp:
healthCheck:
# UDP health checks are tricky and often rely on sending a specific packet and expecting a response.
# Traefik's UDP health check is basic and might not be suitable for all protocols.
# For simplicity, we might disable it or use a very basic check if the protocol supports it.
# If not enabled, Traefik assumes the server is healthy.
scheme: "udp"
path: "/" # Path is not directly applicable to UDP but required by the config structure.
interval: "10s"
timeout: "3s"
# A UDP health check typically involves sending a payload and expecting a specific response.
# The 'port' here refers to the port Traefik sends the health check to on the backend server.
# This is usually the same as the service address port.
port: 53
# If your UDP service has a specific health check payload, you'd configure it here.
# Example: if your service responds to an empty UDP packet with a specific handshake.
# payload: "HEALTHCHECK" # This is illustrative; actual payload depends on the service.
In this setup, Traefik listens on UDP port 53. Any UDP traffic arriving on this port is directed by dns-router to the dns-service. The dns-service then load balances these UDP packets across two backend servers: 192.168.1.100:53 and 192.168.1.101:53. The HostSNI(\*\) rule is a common pattern for UDP because UDP doesn’t have the concept of HTTP Host headers. If you were routing based on something like DNS query names, you’d need a more advanced plugin or a different approach, as Traefik’s built-in UDP rules are quite basic.
The core problem Traefik’s UDP routing solves is acting as a highly available, load-balanced, and potentially TLS-terminating (though less common for pure UDP) entry point for protocols that don’t use TCP. Think of services like DNS (BIND, CoreDNS), game servers (Minecraft, Valheim), or VPN protocols that might use UDP. Without Traefik, you’d typically manage direct UDP load balancing with other tools like HAProxy or keepalived, or rely on cloud provider load balancers. Traefik integrates this into its existing routing and configuration paradigm, managed through its dynamic configuration (like Docker labels or Kubernetes CRDs) alongside your HTTP services.
Internally, Traefik establishes a UDP listener on the specified entry point. When a UDP packet arrives, it matches it against configured routers. For UDP routers, the rule needs to be something that can be evaluated against the incoming packet. HostSNI(\*) is often a fallback for UDP, meaning "match any UDP traffic on this entry point." The packet is then forwarded to the service defined in the router. The service, if it’s a load balancer, selects one of its backend servers based on its configuration (round-robin by default for UDP) and forwards the UDP packet to that server. Crucially, Traefik maintains the UDP session state minimally; it’s largely connectionless, so each packet is treated independently or as part of a very short-lived flow.
The most surprising thing about Traefik’s UDP support is how it handles state and health checks. Unlike TCP, UDP is connectionless, meaning there’s no handshake to establish a persistent connection. Traefik treats each UDP packet as a new event. This means its load balancing is typically round-robin or similar stateless strategies. For health checks, Traefik’s UDP health check mechanism is rudimentary. It often requires sending a specific UDP payload and expecting a specific response, which many UDP services don’t natively support in a standardized way for health checks. This means you might disable UDP health checks and rely on other mechanisms or accept that Traefik might send traffic to an unhealthy UDP server for a short period until the connection times out or the server fails to respond to subsequent packets.
The exact levers you control are the entryPoints (which UDP ports Traefik listens on), the routers (how incoming UDP traffic is matched to a service), and the services (where the UDP traffic is ultimately sent, including the backend server addresses and load balancing strategy). For UDP, the rule in routers is often simplified, and health checks require careful consideration of the specific UDP protocol.
If you’re using Traefik’s Docker provider and trying to expose a UDP service, you’ll need to ensure your Docker Compose file correctly labels the service for Traefik to discover it.