caddyserver/caddy · error
TLS listener wrapper can only be specified once
Error message
TLS listener wrapper can only be specified once
What it means
The tls placeholder wrapper may appear at most once in the listener_wrappers chain because it is only a position marker, not a real wrapper. A second occurrence makes the intended wrapper ordering ambiguous, so provisioning fails.
Source
Thrown at modules/caddyhttp/app.go:338
}
// 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
// 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 {View on GitHub (pinned to 50e54ee279)
Solutions
- Delete all but one tls entry, keeping it where TLS termination should occur
- If unsure, remove it entirely — Caddy prepends it automatically when absent
Example fix
// before "listener_wrappers": ["proxy_protocol", "tls", "tls"] // after "listener_wrappers": ["proxy_protocol", "tls"]
Defensive patterns
Strategy: validation
Validate before calling
count := 0
for _, w := range srvCfg.ListenerWrappers {
if w == "tls" { count++ }
}
if count > 1 { return fmt.Errorf("tls wrapper specified %d times", count) } Prevention
- Keep at most one tls marker per server config
- Lint merged configs for duplicated marker entries
When it happens
Trigger: Listing the tls wrapper two or more times in servers.<name>.listener_wrappers, e.g. ["proxy_protocol", "tls", {"wrapper":"..."}, "tls"].
Common situations: Merging config fragments that each already contained a tls marker; hand-editing JSON and duplicating the entry.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- it is unnecessary to specify the TLS listener wrapper in the
- consolidating TLS connection policies for server %d: %v
- server %s: setting up TLS connection policies: %v
- loading certificate loader modules: %s
- provisioning automation policy %d: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/cefae138dee5e665.
Report an issue: GitHub.