caddyserver/caddy · error

no server TLS configuration available for ClientHello: %+v

Error message

no server TLS configuration available for ClientHello: %+v

What it means

Returned by the GetConfigForClient callback when a ClientHello arrived that no connection policy matched (and no default policy applied). The %+v payload dumps the whole ClientHelloInfo (server name, supported versions, ALPN, cipher suites, remote address), which is the key diagnostic: it shows exactly what the client asked for.

Source

Thrown at modules/caddytls/connpolicy.go:139

		possiblePolicies := cp
		if indexedPolicies, ok := indexedBySNI[asciiServerNameForMatch(hello.ServerName)]; ok {
			possiblePolicies = indexedPolicies
		}

	policyLoop:
		for _, pol := range possiblePolicies {
			for _, matcher := range pol.matchers {
				if !matcher.Match(hello) {
					continue policyLoop
				}
			}
			if pol.Drop {
				return nil, fmt.Errorf("dropping connection")
			}
			return pol.TLSConfig, nil
		}

		return nil, fmt.Errorf("no server TLS configuration available for ClientHello: %+v", hello)
	}

	tlsCfg := &tls.Config{
		MinVersion:         tls.VersionTLS12,
		GetConfigForClient: getConfigForClient,
	}

	// enable ECH, if configured
	if tlsAppIface, err := ctx.AppIfConfigured("tls"); err == nil {
		tlsApp := tlsAppIface.(*TLS)

		if tlsApp.EncryptedClientHello != nil && len(tlsApp.EncryptedClientHello.configs) > 0 {
			// if no publication was configured, we apply ECH to all server names by default,
			// but the TLS app needs to know what they are in this case, since they don't appear
			// in its config (remember, TLS connection policies are used by *other* apps to
			// run TLS servers) -- we skip names with placeholders
			if tlsApp.EncryptedClientHello.Publication == nil {
				repl := caddy.NewReplacer()

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Look at the ServerName in the dumped ClientHello and compare it to the matchers (sni values) of each policy
  2. Add a policy (or site block) covering that name, or a final catch-all policy
  3. For SNI-less clients (IP connections), add a policy matching on no-SNI or set a default SNI
  4. For deep subdomains, add explicit names or restructure matching since *.example.com does not match a.b.example.com

Example fix

# before
example.com {
	tls {
		policies {
			policy {
				match { sni example.com }
			}
		}
	}
}

# after (catch-all for anything else)
example.com {
	tls {
		policies {
			policy {
				match { sni example.com }
			}
			policy {
				match { sni * }
			}
		}
	}
}
Defensive patterns

Strategy: validation

Validate before calling

# Enumerate SNIs your policies cover and diff against expected hostnames:
caddy adapt --config Caddyfile --adapter caddyfile 2>/dev/null \
  | grep -o '"sni"[^}]*}' | sort -u

Prevention

When it happens

Trigger: ServerName-based policy index miss: the ClientHello's SNI is absent from all policies and no catch-all policy exists; client sends no SNI while all policies require sni matching; wildcards that do not cover the requested name (e.g. policy for *.example.com and client asks a.b.example.com).

Common situations: New domain pointed at the server but not added to the config; clients connecting by IP (empty SNI); a policy list tightened so the default fallback was removed; multi-level subdomain not covered by a single-level wildcard.

Understand the failure class

Related errors


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