caddyserver/caddy · error
%s: entry %q contains non-ASCII characters
Error message
%s: entry %q contains non-ASCII characters
What it means
provisionHeaderAliasAllowlist validates header-alias allowlist entries (e.g. for the header-alias filter on requests) at server provisioning. It rejects any entry containing a byte >= 0x80 because Go's net/http parser returns 400 for non-ASCII header names, so such an entry could never match a real request. The %s is the directive name ('_' or '.' separated variants) and %q the offending entry.
Source
Thrown at modules/caddyhttp/server.go:394
allow string // canonical allowed form, e.g. "Webhook_" or "Webhook."
drop string // canonical hyphenated form, e.g. "Webhook-"
}
// provisionHeaderAliasAllowlist validates entries for a header-alias
// allowlist (ExpectedUnderscoreHeaders or ExpectedDotHeaders) and builds
// the precomputed maps and prefix rules used by the hot-path filter in
// serveHTTP. sep is the separator the entries must contain ('_' or '.');
// directive is the Caddyfile/JSON name used in error messages.
func provisionHeaderAliasAllowlist(entries []string, sep rune, directive string) (exactAllow, exactDrop map[string]struct{}, prefixRules []aliasPrefixRule, err error) {
exactAllow = make(map[string]struct{}, len(entries))
exactDrop = make(map[string]struct{}, len(entries))
for _, entry := range entries {
// Reject non-ASCII bytes: Go's HTTP parser returns 400 for
// non-ASCII header names, so such entries can never match.
for i := 0; i < len(entry); i++ {
if entry[i] >= 0x80 {
return nil, nil, nil, fmt.Errorf("%s: entry %q contains non-ASCII characters", directive, entry)
}
}
isGlob := strings.HasSuffix(entry, "*")
name := entry
if isGlob {
name = strings.TrimSuffix(entry, "*")
}
// Reject entries with '*' not at the trailing position.
if strings.ContainsRune(name, '*') {
return nil, nil, nil, fmt.Errorf("%s: entry %q has '*' in an invalid position (only a trailing '*' is allowed)", directive, entry)
}
// The name (without trailing '*') must contain at least one separator.
if !strings.ContainsRune(name, sep) {
return nil, nil, nil, fmt.Errorf("%s: entry %q does not contain a %q", directive, entry, sep)
}View on GitHub (pinned to 50e54ee279)
Solutions
- Rewrite the entry using only ASCII letters, digits, '-', '_' and '.'
- Hex-dump the config region to find hidden non-ASCII bytes (e.g. 'grep -P "[\x80-\xff]" config')
- Disable editor features that produce smart quotes when editing configs
Example fix
// before "-": ["X-Custom Header"] // contains U+00A0 non-breaking space // after "-": ["X-Custom_Header"]
Defensive patterns
Strategy: validation
Validate before calling
func allASCII(entries []string) error {
for _, e := range entries {
for i := 0; i < len(e); i++ {
if e[i] >= 0x80 {
return fmt.Errorf("entry %q has non-ASCII byte at %d", e, i)
}
}
}
return nil
} Prevention
- Edit configs with smart-quotes/dashes disabled
- Scan config files for high bytes: grep -P '[\x80-\xff]' Caddyfile
- Copy header names from RFC-style plain-text sources
When it happens
Trigger: Configuring the header alias allowlist with a header name containing UTF-8 or any high-byte characters, e.g. copied from documentation with a smart quote or non-breaking space.
Common situations: Copy-pasting header names from rich-text docs/websites that insert invisible Unicode characters, or editing configs in editors that save smart quotes.
Related errors
- server %s: %v
- replacement %d for header field '%s': %v
- cannot specify both a substring search and a regular express
- %s: entry %q has '*' in an invalid position (only a trailing
- %s: entry %q does not contain a %q
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/aaf1de7538f7da6b.
Report an issue: GitHub.