caddyserver/caddy · error

mapping has outputs but there are destinations defined

Error message

mapping %d has %d outputs but there are %d destinations defined

What it means

Validate in the map handler enforces 1:1 correspondence between each mapping's `outputs` and the top-level `destinations`: every mapping must have exactly len(destinations) outputs. The message reports the mapping index, its output count, and the destination count.

Solutions

  1. Add or remove outputs in the flagged mapping until they equal the number of destinations, in destination order.
  2. Use "-" (null) for outputs you want to skip on a given mapping.
  3. Recount after any change to the destinations list.

Example fix

// before (Caddyfile)
map {host} {http.vars.up} {http.vars.tier} {
  a.example.com prod
}

// after
map {host} {http.vars.up} {http.vars.tier} {
  a.example.com prod 1
}
Defensive patterns

Strategy: validation

Validate before calling

func outputsMatchDestinations(mappings []Mapping, nDest int) bool {
	for _, m := range mappings {
		if len(m.Outputs) != nDest {
			return false
		}
	}
	return true
}

Prevention

When it happens

Trigger: destinations has 2 entries but a mapping lists 1 or 3 outputs; Caddyfile map line `example.com foo` under a map with two destination placeholders.

Common situations: Adding a second destination ({http.vars.b}) and updating only some mappings; deleting outputs from one mapping during cleanup; misunderstanding that every row must fill every destination.

Related errors


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

Appendix: source

Thrown at modules/caddyhttp/map/map.go:117

		// prevent confusing/ambiguous mappings
		if m.Input != "" && m.InputRegexp != "" {
			return fmt.Errorf("mapping %d has both input and input_regexp fields specified, which is confusing", i)
		}

		// prevent duplicate mappings
		input := m.Input
		if m.InputRegexp != "" {
			input = m.InputRegexp
		}
		if prev, ok := seen[input]; ok {
			return fmt.Errorf("mapping %d has a duplicate input '%s' previously used with mapping %d", i, input, prev)
		}
		seen[input] = i

		// ensure mappings have 1:1 output-to-destination correspondence
		nOut := len(m.Outputs)
		if nOut != nDest {
			return fmt.Errorf("mapping %d has %d outputs but there are %d destinations defined", i, nOut, nDest)
		}
	}

	return nil
}

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	// defer work until a variable is actually evaluated by using replacer's Map callback
	repl.Map(func(key string) (any, bool) {
		// return early if the variable is not even a configured destination
		destIdx := slices.Index(h.Destinations, key)
		if destIdx < 0 {
			return nil, false
		}

		input := repl.ReplaceAll(h.Source, "")

View on GitHub (pinned to 50e54ee279)