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
- Delete the duplicate entry reported at the second index.
- Deduplicate and lowercase host lists in your config-generation step.
- 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
- Deduplicate and lowercase host lists before generating config.
- Remember unicode and punycode forms of the same name collide.
- The matcher is case-insensitive already; one spelling is enough.
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
- converting hostname '%s' to ASCII: %v
- mapping %d has a duplicate input '%s' previously used with m
- unknown object ID '%s'
- decoding request body: %w, at offset %d
- decoding request body: %w
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/480b9180d841c465.
Report an issue: GitHub.