caddyserver/caddy · error

loading packet conn wrapper modules

Error message

loading packet conn wrapper modules: %v

What it means

ctx.LoadModule instantiates servers.<name>.packet_conn_wrappers (modules implementing caddy.PacketConnWrapper, which wrap UDP/QUIC sockets before the QUIC handshake). Any unregistered module ID or failing Provision aborts app provisioning. There is no QUIC placeholder wrapper, unlike the TLS chain.

Solutions

  1. Check the wrapped error for the module name or option at fault
  2. Build with the required plugin (xcaddy build --with ...) or drop the entry
  3. Confirm the module actually implements PacketConnWrapper, not ListenerWrapper
Defensive patterns

Strategy: validation

Validate before calling

// ensure the module is compiled in before load
out, _ := exec.Command("caddy", "list-modules").Output()
for _, w := range srvCfg.PacketConnWrappersRaw {
    id := gjson.GetBytes(w, "wrapper").String()
    if !strings.Contains(string(out), id) {
        return fmt.Errorf("packet conn wrapper %q missing from build", id)
    }
}

Prevention

When it happens

Trigger: A packet_conn_wrappers entry referencing a module not compiled into the binary, a typo'd module ID, or a wrapper whose own provisioning fails on its options.

Common situations: Using QUIC/HTTP/3 with a custom packet-filtering plugin that is missing from the build; copying configs between builds with different plugins.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/3a09e979e8255539. Report an issue: GitHub.

Appendix: source

Thrown at modules/caddyhttp/app.go:357

					}
					hasTLSPlaceholder = true
				}
				srv.listenerWrappers = append(srv.listenerWrappers, val.(caddy.ListenerWrapper))
			}
			// if any wrappers were configured but the TLS placeholder wrapper is
			// absent, prepend it so all defined wrappers come after the TLS
			// handshake; this simplifies logic when starting the server, since we
			// can simply assume the TLS placeholder will always be there
			if !hasTLSPlaceholder && len(srv.listenerWrappers) > 0 {
				srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
			}
		}

		// set up each packet conn modifier
		if srv.PacketConnWrappersRaw != nil {
			vals, err := ctx.LoadModule(srv, "PacketConnWrappersRaw")
			if err != nil {
				return fmt.Errorf("loading packet conn wrapper modules: %v", err)
			}
			// if any wrappers were configured, they come before the QUIC handshake;
			// unlike TLS above, there is no QUIC placeholder
			for _, val := range vals.([]any) {
				srv.packetConnWrappers = append(srv.packetConnWrappers, val.(caddy.PacketConnWrapper))
			}
		}

		// pre-compile the primary handler chain, and be sure to wrap it in our
		// route handler so that important security checks are done, etc.
		primaryRoute := emptyHandler
		if srv.Routes != nil {
			err := srv.Routes.ProvisionHandlers(ctx, app.Metrics)
			if err != nil {
				return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
			}
			primaryRoute = srv.Routes.Compile(emptyHandler)
		}

View on GitHub (pinned to 50e54ee279)