caddyserver/caddy · error
header %d: invalid format \"%s\" (expecting \"Field: value\"
Error message
header %d: invalid format \"%s\" (expecting \"Field: value\")
What it means
Thrown by Caddy's CLI 'respond' command (caddy respond --header flag) when a header argument cannot be split into a field and value. The code uses strings.Cut on ':' and requires both sides to be non-empty after trimming. It exists to reject malformed Header:Value pairs before the ad-hoc server starts.
Source
Thrown at modules/caddyhttp/staticresp.go:380
bodyBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
body = string(bodyBytes)
}
}
// build headers map
headers, err := fl.GetStringArray("header")
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err)
}
hdr := make(http.Header)
for i, h := range headers {
key, val, found := strings.Cut(h, ":")
key, val = strings.TrimSpace(key), strings.TrimSpace(val)
if !found || key == "" || val == "" {
return caddy.ExitCodeFailedStartup, fmt.Errorf("header %d: invalid format \"%s\" (expecting \"Field: value\")", i, h)
}
hdr.Set(key, val)
}
// build each HTTP server
httpApp := App{Servers: make(map[string]*Server)}
// expand listen address, if more than one port
listenAddr, err := caddy.ParseNetworkAddress(listen)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() {
listenAddrs := make([]string, 0, listenAddr.PortRangeSize())
for offset := uint(0); offset < listenAddr.PortRangeSize(); offset++ {
listenAddrs = append(listenAddrs, listenAddr.JoinHostPort(offset))
}View on GitHub (pinned to 50e54ee279)
Solutions
- Rewrite the flag value as 'Field: value', e.g. --header "Content-Type: application/json", with text on both sides of the colon
- Check shell quoting: the whole 'Field: value' string must reach Caddy as one argument
- Remove duplicate colons at the start or values that are only whitespace
- If you intended an empty-valued header (rare), use a real Caddyfile with the header directive instead of caddy respond
Example fix
# before caddy respond --listen :8080 --header "X-Empty" "hi" # or caddy respond --header "X-Val:" # after caddy respond --listen :8080 --header "X-Empty: hi"
Defensive patterns
Strategy: validation
Validate before calling
# bash: validate each --header before invoking caddy
for h in "Content-Type: application/json" "X-Empty"; do
case "$h" in
*:*$'\n'*) : ;;
esac
key="${h%%:*}"; val="${h#*:}"
key="${key// /}"; [ -n "$key" ] || { echo "bad header (empty key): $h" >&2; exit 1; }
val="$(echo "$val" | sed 's/^ *//;s/ *$//')"
[ -n "$val" ] || { echo "bad header (empty value): $h" >&2; exit 1; }
done Prevention
- Wrap caddy respond invocations in scripts with a header-format precheck loop
- Quote each --header argument as a single shell word
- Never pass curl-style 'Header:' (empty value) syntax to caddy respond
When it happens
Trigger: Running `caddy respond --header "Foo" ...` (no colon), `--header "Foo: "` or `--header ": bar"` (empty key or value after TrimSpace). Only header strings where key or val is empty after trimming, or no ':' at all, fail; multiple 'a:b:c' is fine because Cut splits on the first colon.
Common situations: Shell quoting mistakes that swallow the colon, trailing whitespace-only values, copy-pasting header syntax from curl (-H "Accept:") which means 'remove header' and has no empty value here, or forgetting that the flag requires exactly 'Field: value'.
Related errors
- parsing environment file: %v
- invalid header flag: %v
- invalid admin address %s: %v
- caddy responded with error: HTTP %d: %s
- no config file to load; either use --config flag or ensure C
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/d8af5ed2d4b361ca.
Report an issue: GitHub.