caddyserver/caddy · error

loading handshake matchers: %v

Error message

loading handshake matchers: %v

What it means

Returned by ConnectionPolicies.Provision when ctx.LoadModule fails to instantiate one of the handshake matchers listed in a connection policy's MatchersRaw map (e.g. tls.handshake_match.sni, tls.handshake_match.alpn, tls.handshake_match.remote_ip). The wrapped error usually identifies the module name and the underlying cause (unknown module, bad subconfig, provisioning failure inside the matcher).

Source

Thrown at modules/caddytls/connpolicy.go:57

func init() {
	caddy.RegisterModule(LeafCertClientAuth{})
}

// ConnectionPolicies govern the establishment of TLS connections. It is
// an ordered group of connection policies; the first matching policy will
// be used to configure TLS connections at handshake-time.
type ConnectionPolicies []*ConnectionPolicy

// Provision sets up each connection policy. It should be called
// during the Validate() phase, after the TLS app (if any) is
// already set up.
func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
	for i, pol := range cp {
		// matchers
		mods, err := ctx.LoadModule(pol, "MatchersRaw")
		if err != nil {
			return fmt.Errorf("loading handshake matchers: %v", err)
		}
		for _, modIface := range mods.(map[string]any) {
			cp[i].matchers = append(cp[i].matchers, modIface.(ConnectionMatcher))
		}

		// enable HTTP/2 by default
		if pol.ALPN == nil {
			pol.ALPN = append(pol.ALPN, defaultALPN...)
		}

		// pre-build standard TLS config so we don't have to at handshake-time
		err = pol.buildStandardTLSConfig(ctx)
		if err != nil {
			return fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
		}

		if pol.ClientAuthentication != nil && len(pol.ClientAuthentication.VerifiersRaw) > 0 {
			clientCertValidations, err := ctx.LoadModule(pol.ClientAuthentication, "VerifiersRaw")

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the wrapped error: it names the module that failed and why
  2. Run 'caddy list-modules' and confirm every tls.handshake_match.* referenced in the config is present
  3. If a plugin matcher is missing, rebuild with xcaddy including the plugin, or remove that matcher
  4. Fix the matcher's arguments (e.g. empty SNI value, malformed IP range in remote_ip)

Example fix

# before (binary built without the plugin)
{
	"match": {
		"sni": ["example.com"],
		"my_custom_matcher": {...}
	}
}

# after (custom matcher removed or plugin built in via xcaddy)
{
	"match": {
		"sni": ["example.com"]
	}
}
Defensive patterns

Strategy: try-catch

Validate before calling

# Pre-flight check that every matcher module referenced exists:
caddy list-modules | grep tls.handshake_match

Try / catch

// When provisioning programmatically:
if err := connPolicies.Provision(ctx); err != nil {
	if strings.HasPrefix(err.Error(), "loading handshake matchers:") {
		// a matcher module failed to load: inspect config's matchers map
	}
	return err
}

Prevention

When it happens

Trigger: JSON config with a matchers object referencing a module ID that is not compiled into the binary (e.g. a plugin matcher without the plugin built in), or a matcher whose own Provision fails. Via Caddyfile: an SNI/ALPN matcher whose inline values are malformed so the matcher's UnmarshalCaddyfile produces an error.

Common situations: Custom build (xcaddy) missing a plugin that the config was written for; migrating a config between Caddy versions where a matcher module was renamed or its schema changed; typo in the matcher name.

Understand the failure class

Related errors


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