{"record":{"id":"177bad6fa1d47e9e","repo":"caddyserver/caddy","slug":"making-udp-socket-for-http-3-transport-v","errorCode":null,"errorMessage":"making udp socket for HTTP/3 transport: %v","messagePattern":"making udp socket for HTTP/3 transport: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/reverseproxy/httptransport.go","lineNumber":509,"sourceCode":"\n\t// configure HTTP/3 transport if enabled; however, this does not\n\t// automatically fall back to lower versions like most web browsers\n\t// do (that'd add latency and complexity, besides, we expect that\n\t// site owners  control the backends), so it must be exclusive\n\tif len(h.Versions) == 1 && h.Versions[0] == \"3\" {\n\t\th.h3Transport = new(http3.Transport)\n\t\tif h.TLS != nil {\n\t\t\tvar err error\n\t\t\th.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"making TLS client config for HTTP/3 transport: %v\", err)\n\t\t\t}\n\n\t\t\tif strings.Contains(h.TLS.ServerName, \"{\") {\n\t\t\t\t// copied from quic-go\n\t\t\t\tudpConn, err := net.ListenUDP(\"udp\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"making udp socket for HTTP/3 transport: %v\", err)\n\t\t\t\t}\n\t\t\t\th.quicTransport = &quic.Transport{Conn: udpConn}\n\t\t\t\th.h3Transport.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {\n\t\t\t\t\t// tlsCfg is already cloned from h3Transport.TLSClientConfig\n\t\t\t\t\trepl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)\n\t\t\t\t\ttlsCfg.ServerName = repl.ReplaceAll(tlsCfg.ServerName, \"\")\n\t\t\t\t\tudpAddr, err := resolveUDPAddr(ctx, \"udp\", addr)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\treturn h.quicTransport.DialEarly(ctx, udpAddr, tlsCfg, cfg)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if len(h.Versions) > 1 && slices.Contains(h.Versions, \"3\") {\n\t\treturn nil, fmt.Errorf(\"if HTTP/3 is enabled to the upstream, no other HTTP versions are supported\")\n\t}\n","sourceCodeStart":491,"sourceCodeEnd":527,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/reverseproxy/httptransport.go#L491-L527","documentation":"Thrown while provisioning a reverse_proxy transport with HTTP/3 enabled and a placeholder in tls_server_name. Because the QUIC connection must re-resolve the SNI per request, Caddy builds a dedicated quic.Transport backed by a local UDP socket; if the OS refuses to open that socket (net.ListenUDP fails), provisioning of the whole transport aborts with this wrapped error. It is an environment/resource failure, not a config-syntax error.","triggerScenarios":"reverse_proxy with transport http { versions 3 } (or h3) plus a tls block whose server_name contains a '{...}' placeholder. The code path at httptransport.go calls net.ListenUDP(\"udp\", nil), which fails when the process lacks permission to create UDP sockets, the UDP port space is exhausted, or a container/seccomp policy blocks socket(2)/bind(2) on UDP.","commonSituations":"Running Caddy in a hardened container (gVisor, restricted seccomp profile, no-network k8s sandbox) with HTTP/3 upstreams and dynamic SNI placeholders; UDP buffers or ephemeral ports exhausted; SELinux/AppArmor denying datagram sockets.","solutions":["Check that the runtime environment permits UDP sockets: run a minimal test (e.g. a tiny Go program calling net.ListenUDP) under the same container/user; loosen the seccomp/AppArmor/SELinux rule that blocks it.","If you do not need per-request dynamic SNI, remove the placeholder from tls_server_name (use a literal value) so the shared quic-go dialer is used and no extra UDP socket is created.","If dynamic SNI is not required at all, drop versions 3 from transport http and proxy over HTTP/1.1 or H2 until the environment supports QUIC.","Raise ephemeral port range / UDP memlock limits (sysctl net.ipv4.ip_local_port_range, rlimit memlock) if the failure is resource exhaustion."],"exampleFix":"// before (Caddyfile)\nreverse_proxy localhost:443 {\n    transport http {\n        versions 3\n        tls\n        tls_server_name {http.request.host}\n    }\n}\n\n// after\nreverse_proxy localhost:443 {\n    transport http {\n        versions 3\n        tls\n        tls_server_name example.internal   // literal SNI: no extra UDP socket needed\n    }\n}","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm the process can open a UDP socket before loading H3 config\nconn, err := net.ListenUDP(\"udp\", nil)\nif err != nil {\n    log.Fatalf(\"HTTP/3 upstream requires UDP sockets: %v\", err)\n}\nconn.Close()","typeGuard":null,"tryCatchPattern":"// In code embedding Caddy, treat config-load errors as transient only for this case\nerr := caddy.Load(cfg, false)\nif err != nil && strings.Contains(err.Error(), \"making udp socket for HTTP/3 transport\") {\n    // environment-level fix required (seccomp/limits); retry only after remediation\n}\nif err != nil {\n    return err\n}","preventionTips":["Test UDP socket creation in the target container image before enabling versions 3.","Avoid placeholder server_name with H3 upstreams unless per-request SNI is truly required.","Set sane sysctl/rlimits for UDP (ip_local_port_range, memlock) in QUIC deployments."],"tags":["network","udp","quic","http3","reverseproxy","environment"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}