caddyserver/caddy · error

marshaling %T matcher: %v

Error message

marshaling %T matcher: %v

What it means

After successfully parsing Caddyfile matcher definitions, parseMatcherDefinitions marshals each matcher back to JSON so the adapted config contains it. If json.Marshal fails on the matcher value — typically custom types with unmarshalable fields (func, chan, complex, or cycles) or a broken MarshalJSON — this error reports the Go type and the marshal error.

Source

Thrown at modules/caddyhttp/matchers.go:1730

		if rm, ok := unm.(RequestMatcherWithError); ok {
			matcherMap[matcherName] = rm
			continue
		}
		if rm, ok := unm.(RequestMatcher); ok {
			matcherMap[matcherName] = rm
			continue
		}
		return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
	}

	// we should now have a functional matcher, but we also
	// need to be able to marshal as JSON, otherwise config
	// adaptation will be missing the matchers!
	matcherSet := make(caddy.ModuleMap)
	for name, matcher := range matcherMap {
		jsonBytes, err := json.Marshal(matcher)
		if err != nil {
			return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
		}
		matcherSet[name] = jsonBytes
	}

	return matcherSet, nil
}

var wordRE = regexp.MustCompile(`\w+`)

const regexpPlaceholderPrefix = "http.regexp"

// MatcherErrorVarKey is the key used for the variable that
// holds an optional error emitted from a request matcher,
// to short-circuit the handler chain, since matchers cannot
// return errors via the RequestMatcher interface.
//
// Deprecated: Matchers should implement RequestMatcherWithError
// which can return an error directly, instead of smuggling it

View on GitHub (pinned to 50e54ee279)

Solutions

  1. In the plugin, implement json.Marshaler (MarshalJSON) that emits the original config form, not runtime state.
  2. Keep runtime-only fields unexported and ensure the rest are plain JSON-serializable types.
  3. Update the plugin to a version compatible with your Caddy release.

Example fix

// before (plugin)
type Fancy struct {
  Re *regexp.Regexp `json:"-"`
}

// after
type Fancy struct {
  Pattern string `json:"pattern"`
  Re *regexp.Regexp `json:"-"`
}
func (f Fancy) MarshalJSON() ([]byte, error) { return json.Marshal(f.Pattern) }
Defensive patterns

Strategy: try-catch

Try / catch

// Plugin authors: make marshaling deterministic and error-free
func (m MyMatcher) MarshalJSON() ([]byte, error) {
	type plain MyMatcher // drop methods, keep fields
	return json.Marshal(plain(m))
}

Prevention

When it happens

Trigger: A third-party matcher holding a func/chan field or an unexported state that its MarshalJSON mishandles; a matcher type whose MarshalJSON returns an error for certain configs.

Common situations: Plugin development where the struct is provisioned into a runtime form (compiled regex, locks) with no custom marshaling back; cyclic references in matcher structs; plugin version drift after Caddy changed expected JSON shapes.

Related errors


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