{"record":{"id":"d6cd9bb83f1aabae","repo":"caddyserver/caddy","slug":"matcher-module-s-is-not-a-connection-matcher","errorCode":null,"errorMessage":"matcher module '%s' is not a connection matcher","messagePattern":"matcher module '(.+?)' is not a connection matcher","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/connpolicy.go","lineNumber":1116,"sourceCode":"\tmatcherMap := make(map[string]ConnectionMatcher)\n\n\ttokensByMatcherName := make(map[string][]caddyfile.Token)\n\tfor nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {\n\t\tmatcherName := d.Val()\n\t\ttokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)\n\t}\n\n\tfor matcherName, tokens := range tokensByMatcherName {\n\t\tdd := caddyfile.NewDispenser(tokens)\n\t\tdd.Next() // consume wrapper name\n\n\t\tunm, err := caddyfile.UnmarshalModule(dd, \"tls.handshake_match.\"+matcherName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcm, ok := unm.(ConnectionMatcher)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"matcher module '%s' is not a connection matcher\", matcherName)\n\t\t}\n\t\tmatcherMap[matcherName] = cm\n\t}\n\n\tmatcherSet := make(caddy.ModuleMap)\n\tfor name, matcher := range matcherMap {\n\t\tjsonBytes, err := json.Marshal(matcher)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"marshaling %T matcher: %v\", matcher, err)\n\t\t}\n\t\tmatcherSet[name] = jsonBytes\n\t}\n\n\treturn matcherSet, nil\n}\n","sourceCodeStart":1098,"sourceCodeEnd":1132,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/connpolicy.go#L1098-L1132","documentation":"When parsing a Caddyfile connection policy's matcher subdirectives (tls { match <name> ... }), each named module is unmarshaled from namespace tls.handshake_match.* and must implement the ConnectionMatcher interface (a Match(*tls.ClientHelloInfo) bool method). If the module resolves but does not implement that interface, parsing fails.","triggerScenarios":"A custom or misregistered module under tls.handshake_match that lacks the Match method; a plugin namespace typo causing the wrong module to be resolved.","commonSituations":"XCaddy plugins implementing handshake matchers against an older/newer interface; rarely hit with stock Caddy since only built-in matchers exist.","solutions":["If using a plugin matcher, rebuild it against your Caddy version and add the guard var _ caddytls.ConnectionMatcher = (*MyMatcher)(nil)","Use only supported matchers in tls match blocks (e.g. 'match remote_ip ...')","Check module registration: caddy list-modules | grep tls.handshake_match"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// (shell) confirm matcher modules are present\ncaddy list-modules | grep '^tls\\.handshake_match\\.'","typeGuard":"var _ caddytls.ConnectionMatcher = (*MyMatcher)(nil)\n\nfunc (m *MyMatcher) Match(chi *tls.ClientHelloInfo) bool { return true }","tryCatchPattern":null,"preventionTips":["Add interface guards in custom matcher plugins","Prefer standard matchers (remote_ip, sni) available in stock builds","Rebuild xcaddy plugins on every core upgrade"],"tags":["tls","caddyfile","module-system","xcaddy"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}