{"record":{"id":"19c59e1651affe1a","repo":"caddyserver/caddy","slug":"it-is-unnecessary-to-specify-the-tls-listener-wrap","errorCode":null,"errorMessage":"it is unnecessary to specify the TLS listener wrapper in the first position because that is the default","messagePattern":"it is unnecessary to specify the TLS listener wrapper in the first position because that is the default","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/app.go","lineNumber":335,"sourceCode":"\t\t\t\treturn fmt.Errorf(\"server %s, listener %d: %v\", srvName, i, err)\n\t\t\t}\n\t\t\tsrv.Listen[i] = lnOut\n\t\t}\n\n\t\t// set up each listener modifier\n\t\tif srv.ListenerWrappersRaw != nil {\n\t\t\tvals, err := ctx.LoadModule(srv, \"ListenerWrappersRaw\")\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"loading listener wrapper modules: %v\", err)\n\t\t\t}\n\t\t\tvar hasTLSPlaceholder bool\n\t\t\tfor i, val := range vals.([]any) {\n\t\t\t\tif _, ok := val.(*tlsPlaceholderWrapper); ok {\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\t// putting the tls placeholder wrapper first is nonsensical because\n\t\t\t\t\t\t// that is the default, implicit setting: without it, all wrappers\n\t\t\t\t\t\t// will go after the TLS listener anyway\n\t\t\t\t\t\treturn fmt.Errorf(\"it is unnecessary to specify the TLS listener wrapper in the first position because that is the default\")\n\t\t\t\t\t}\n\t\t\t\t\tif hasTLSPlaceholder {\n\t\t\t\t\t\treturn fmt.Errorf(\"TLS listener wrapper can only be specified once\")\n\t\t\t\t\t}\n\t\t\t\t\thasTLSPlaceholder = true\n\t\t\t\t}\n\t\t\t\tsrv.listenerWrappers = append(srv.listenerWrappers, val.(caddy.ListenerWrapper))\n\t\t\t}\n\t\t\t// if any wrappers were configured but the TLS placeholder wrapper is\n\t\t\t// absent, prepend it so all defined wrappers come after the TLS\n\t\t\t// handshake; this simplifies logic when starting the server, since we\n\t\t\t// can simply assume the TLS placeholder will always be there\n\t\t\tif !hasTLSPlaceholder && len(srv.listenerWrappers) > 0 {\n\t\t\t\tsrv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)\n\t\t\t}\n\t\t}\n\n\t\t// set up each packet conn modifier","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/app.go#L317-L353","documentation":"The special 'tls' placeholder listener wrapper marks where TLS termination happens in the wrapper chain. Position 0 is the implicit default (all wrappers after the TLS handshake), so specifying it first is rejected as redundant. This is a pure configuration sanity check, not a runtime failure.","triggerScenarios":"Putting the tls placeholder wrapper in the first element of servers.<name>.listener_wrappers, e.g. [\"tls\", \"proxy_protocol\"].","commonSituations":"Users copy an example config that shows the tls marker and place it first 'for clarity'; misunderstanding that wrapper order is significant only when some wrappers must run before TLS.","solutions":["Remove the tls entry from the first position — omit it entirely, or move it after the wrappers that must see raw/pretLS traffic (e.g. proxy_protocol)","Re-validate the config"],"exampleFix":"// before\n\"listener_wrappers\": [\"tls\", {\"wrapper\":\"proxy_protocol\"}]\n// after\n\"listener_wrappers\": [{\"wrapper\":\"proxy_protocol\"}, \"tls\"]","handlingStrategy":"validation","validationCode":"wrappers := srvCfg.ListenerWrappers\nif len(wrappers) > 0 && wrappers[0] == \"tls\" {\n    return fmt.Errorf(\"tls wrapper must not be first (it is the default)\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Omit the tls marker unless a wrapper must run before the TLS handshake","Review generated listener_wrappers arrays after adapting Caddyfiles that use global options"],"tags":["caddy","tls","listener-wrappers","config"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}