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

  1. Rewrite the flag value as 'Field: value', e.g. --header "Content-Type: application/json", with text on both sides of the colon
  2. Check shell quoting: the whole 'Field: value' string must reach Caddy as one argument
  3. Remove duplicate colons at the start or values that are only whitespace
  4. 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

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


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