caddyserver/caddy · error

the 'forwarded' option is no longer supported; use the…

Error message

the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead

What it means

Before publishing ECH configs, Caddy serializes the config list to its binary DNS wire format via echCfgList.MarshalBinary (the crypto/tls EncryptedClientHelloConfigList encoding). Marshal failed, meaning one of the in-memory ECH configs is malformed for wire encoding (bad config bytes, unsupported version, or an empty list edge case).

Solutions

  1. Unwrap and inspect the MarshalBinary error for the offending config (often indicates a specific record length/version problem).
  2. Stop Caddy and reset the ech/configs storage folder so fresh, well-formed configs are generated, then let publication repopulate DNS records.
  3. After Caddy version upgrades with ECH enabled, expect rotation to replace old configs; if marshal fails immediately post-upgrade, reset state.
  4. Verify no external tooling writes into Caddy's storage.

Example fix

# before: publication fails on corrupt stored config
# after: regenerate ECH state
systemctl stop caddy && rm -rf /var/lib/caddy/ech/configs && systemctl start caddy
Defensive patterns

Strategy: fallback

Try / catch

On marshal failure of the config list, fall back to resetting ECH state: stop Caddy, back up and remove ech/configs, restart — fresh configs marshal cleanly and republish to DNS.

Prevention

When it happens

Trigger: tls.EncryptedClientHelloConfigList built from stored configs fails MarshalBinary: a config.bin entry loaded from storage is truncated/corrupt in a way that survived the load cleanup path, or a version/length field is inconsistent.

Common situations: Storage corruption of config.bin; a Caddy upgrade changing the expected binary layout while old configs remain in storage; hand-edited storage.

Related errors


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

Appendix: source

Thrown at modules/caddyhttp/ip_matchers.go:121

		// name of the macro, this is the function name that users see when writing expressions.
		"remote_ip",
		// name of the function that the macro will be rewritten to call.
		"remote_ip_match_request_list",
		// internal data type of the MatchPath value.
		[]*cel.Type{cel.ListType(cel.StringType)},
		// function to convert a constant list of strings to a MatchPath instance.
		func(data ref.Val) (RequestMatcherWithError, error) {
			refStringList := stringSliceType
			strList, err := data.ConvertToNative(refStringList)
			if err != nil {
				return nil, err
			}

			m := MatchRemoteIP{}

			for _, input := range strList.([]string) {
				if input == "forwarded" {
					return nil, errors.New("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
				}
				m.Ranges = append(m.Ranges, input)
			}

			err = m.Provision(ctx)
			return m, err
		},
	)
}

// Provision parses m's IP ranges, either from IP or CIDR expressions.
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
	m.logger = ctx.Logger()
	cidrs, zones, err := provisionCidrsZonesFromRanges(m.Ranges)
	if err != nil {
		return err
	}
	m.cidrs = cidrs

View on GitHub (pinned to 50e54ee279)