caddyserver/caddy · warning

host at index %d is repeated at index %d: %s

Error message

host at index %d is repeated at index %d: %s

What it means

MatchHost.Provision lowercases and ASCII-normalizes each host, then rejects duplicates — they are nonsensical in a matcher and would only slow down matching. The error reports the first index, the repeat index, and the host string. Case differences and unicode/punycode forms count as duplicates because normalization happens first.

Source

Thrown at modules/caddyhttp/matchers.go:267

			return d.Err("malformed host matcher: blocks are not supported")
		}
	}
	return nil
}

// Provision sets up and validates m, including making it more efficient for large lists.
func (m MatchHost) Provision(_ caddy.Context) error {
	// check for duplicates; they are nonsensical and reduce efficiency
	// (we could just remove them, but the user should know their config is erroneous)
	seen := make(map[string]int, len(m))
	for i, host := range m {
		asciiHost, err := idna.ToASCII(host)
		if err != nil {
			return fmt.Errorf("converting hostname '%s' to ASCII: %v", host, err)
		}
		normalizedHost := strings.ToLower(asciiHost)
		if firstI, ok := seen[normalizedHost]; ok {
			return fmt.Errorf("host at index %d is repeated at index %d: %s", firstI, i, host)
		}
		// Normalize exact hosts for standardized comparison in large-list fastpath later on.
		// Keep wildcards/placeholders untouched.
		if m.fuzzy(asciiHost) {
			m[i] = asciiHost
		} else {
			m[i] = normalizedHost
		}
		seen[normalizedHost] = i
	}

	if m.large() {
		// sort the slice lexicographically, grouping "fuzzy" entries (wildcards and placeholders)
		// at the front of the list; this allows us to use binary search for exact matches, which
		// we have seen from experience is the most common kind of value in large lists; and any
		// other kinds of values (wildcards and placeholders) are grouped in front so the linear
		// search should find a match fairly quickly
		sort.Slice(m, func(i, j int) bool {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Delete the duplicate entry reported at the second index.
  2. Deduplicate and lowercase host lists in your config-generation step.
  3. Remember the matcher is already case-insensitive — one canonical spelling suffices.

Example fix

// before (Caddyfile)
@a host example.com Example.com

// after
@a host example.com
Defensive patterns

Strategy: validation

Validate before calling

import (
	"strings"
	"golang.org/x/net/idna"
)

func noDuplicateHosts(hosts []string) bool {
	seen := map[string]bool{}
	for _, h := range hosts {
		a, err := idna.ToASCII(h)
		if err != nil {
			return false
		}
		k := strings.ToLower(a)
		if seen[k] {
			return false
		}
		seen[k] = true
	}
	return true
}

Prevention

When it happens

Trigger: host list containing "Example.com" and "example.com"; "exämple.com" and its punycode "xn--exmple-cua.com"; the same name twice from a copy-paste.

Common situations: Config assembled from multiple team snippets; case-inconsistent DNS names in docs; unicode vs punycode duplicates after IDNA normalization; automated configs that append hosts without dedup.

Related errors


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