caddyserver/caddy · error

loading listener wrapper modules: %v

Error message

loading listener wrapper modules: %v

What it means

ctx.LoadModule deserializes and instantiates each module in servers.<name>.listener_wrappers. If the raw JSON for any wrapper is not a registered caddy.ListenerWrapper module, or the module's own provisioning fails, the whole app fails to provision. The %v is the underlying module-loading error.

Source

Thrown at modules/caddyhttp/app.go:326

		}
		if err := srv.provisionDotHeaders(); err != nil {
			return fmt.Errorf("server %s: %v", srvName, err)
		}

		// process each listener address
		for i := range srv.Listen {
			lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
			if err != nil {
				return fmt.Errorf("server %s, listener %d: %v", srvName, i, err)
			}
			srv.Listen[i] = lnOut
		}

		// set up each listener modifier
		if srv.ListenerWrappersRaw != nil {
			vals, err := ctx.LoadModule(srv, "ListenerWrappersRaw")
			if err != nil {
				return fmt.Errorf("loading listener wrapper modules: %v", err)
			}
			var hasTLSPlaceholder bool
			for i, val := range vals.([]any) {
				if _, ok := val.(*tlsPlaceholderWrapper); ok {
					if i == 0 {
						// putting the tls placeholder wrapper first is nonsensical because
						// that is the default, implicit setting: without it, all wrappers
						// will go after the TLS listener anyway
						return fmt.Errorf("it is unnecessary to specify the TLS listener wrapper in the first position because that is the default")
					}
					if hasTLSPlaceholder {
						return fmt.Errorf("TLS listener wrapper can only be specified once")
					}
					hasTLSPlaceholder = true
				}
				srv.listenerWrappers = append(srv.listenerWrappers, val.(caddy.ListenerWrapper))
			}
			// if any wrappers were configured but the TLS placeholder wrapper is

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the wrapped error: it names the module or field that failed
  2. Ensure the wrapper plugin is compiled into your binary (xcaddy build with the plugin) or remove it from the config
  3. Verify the module ID matches the plugin docs exactly (e.g. caddy.listeners.*.*)

Example fix

// before
"listener_wrappers": [{"wrapper":"proxy_protocol"}]  // plugin missing
// after
xcaddy build --with github.com/mholt/caddy-l4  # then keep config
Defensive patterns

Strategy: validation

Validate before calling

// verify each listener wrapper module is registered in this build
ids := caddy.ModulesModuleIDs() // or `caddy list-modules` from CLI
for _, w := range srvCfg.ListenerWrappersRaw {
    name := gjson.GetBytes(w, "wrapper").String()
    if !slices.Contains(ids, "caddy.listeners."+name) && !slices.Contains(ids, name) {
        return fmt.Errorf("listener wrapper %q not in build", name)
    }
}

Prevention

When it happens

Trigger: Referencing an unregistered listener wrapper module ID (e.g. from a plugin binary not compiled in), a typo in the module name, or a valid wrapper whose Provision returns an error (bad option values).

Common situations: Using a custom build (xcaddy) that omits a plugin referenced in JSON config; copying configs between Caddy builds with different plugin sets; option name changes across plugin versions.

Related errors


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