caddyserver/caddy · error
making udp socket for HTTP/3 transport: %v
Error message
making udp socket for HTTP/3 transport: %v
What it means
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.
Source
Thrown at modules/caddyhttp/reverseproxy/httptransport.go:509
// configure HTTP/3 transport if enabled; however, this does not
// automatically fall back to lower versions like most web browsers
// do (that'd add latency and complexity, besides, we expect that
// site owners control the backends), so it must be exclusive
if len(h.Versions) == 1 && h.Versions[0] == "3" {
h.h3Transport = new(http3.Transport)
if h.TLS != nil {
var err error
h.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)
if err != nil {
return nil, fmt.Errorf("making TLS client config for HTTP/3 transport: %v", err)
}
if strings.Contains(h.TLS.ServerName, "{") {
// copied from quic-go
udpConn, err := net.ListenUDP("udp", nil)
if err != nil {
return nil, fmt.Errorf("making udp socket for HTTP/3 transport: %v", err)
}
h.quicTransport = &quic.Transport{Conn: udpConn}
h.h3Transport.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
// tlsCfg is already cloned from h3Transport.TLSClientConfig
repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
tlsCfg.ServerName = repl.ReplaceAll(tlsCfg.ServerName, "")
udpAddr, err := resolveUDPAddr(ctx, "udp", addr)
if err != nil {
return nil, err
}
return h.quicTransport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
}
}
}
} else if len(h.Versions) > 1 && slices.Contains(h.Versions, "3") {
return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported")
}
View on GitHub (pinned to 50e54ee279)
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.
Example fix
// before (Caddyfile)
reverse_proxy localhost:443 {
transport http {
versions 3
tls
tls_server_name {http.request.host}
}
}
// after
reverse_proxy localhost:443 {
transport http {
versions 3
tls
tls_server_name example.internal // literal SNI: no extra UDP socket needed
}
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: confirm the process can open a UDP socket before loading H3 config
conn, err := net.ListenUDP("udp", nil)
if err != nil {
log.Fatalf("HTTP/3 upstream requires UDP sockets: %v", err)
}
conn.Close() Try / catch
// In code embedding Caddy, treat config-load errors as transient only for this case
err := caddy.Load(cfg, false)
if err != nil && strings.Contains(err.Error(), "making udp socket for HTTP/3 transport") {
// environment-level fix required (seccomp/limits); retry only after remediation
}
if err != nil {
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- starting HTTP/3 QUIC listener: %v
- loading packet conn wrapper modules: %v
- if HTTP/3 is enabled to the upstream, no other HTTP versions
- parsing CIDR expression: '%s': %v
- network '%s' cannot handle HTTP/3 connections
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/177bad6fa1d47e9e.
Report an issue: GitHub.