caddyserver/caddy · error

mismatched leading whitespace in heredoc <<%s on line #%d [%

Error message

mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker

What it means

In a heredoc, Caddy strips the closing marker's leading indentation from every content line. If a content line's leading whitespace does not exactly start with that padding (wrong character, mixed tabs/spaces, or less indentation than the marker), stripping is unsafe and this error fires, naming the offending line and expected padding.

Source

Thrown at caddyconfig/caddyfile/lexer.go:327

	// figure out how much whitespace we need to strip from the front of every line
	// by getting the string that precedes the marker, on the last line
	paddingToStrip := stringVal[lastNewline+1 : len(stringVal)-len(marker)]

	// iterate over each line and strip the whitespace from the front
	var out string
	for lineNum, lineText := range lines[:len(lines)-1] {
		if lineText == "" || lineText == "\r" {
			out += "\n"
			continue
		}

		// find an exact match for the padding
		index := strings.Index(lineText, paddingToStrip)

		// if the padding doesn't match exactly at the start then we can't safely strip
		if index != 0 {
			cleanLineText := strings.TrimRight(lineText, "\r\n")
			return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, cleanLineText, paddingToStrip)
		}

		// strip, then append the line, with the newline, to the output.
		// also removes all "\r" because Windows.
		out += strings.ReplaceAll(lineText[len(paddingToStrip):]+"\n", "\r", "")
	}

	// Remove the trailing newline from the loop
	if len(out) > 0 && out[len(out)-1] == '\n' {
		out = out[:len(out)-1]
	}

	// return the final value
	return []rune(out), nil
}

// Quoted returns true if the token was enclosed in quotes
// (i.e. double quotes, backticks, or heredoc).

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Make every content line's leading whitespace use the same characters and at least the depth of the closing marker's indentation
  2. Avoid mixing tabs and spaces inside heredocs
  3. Simplest fix: put the closing marker at column 0 and left-align content, or indent both consistently
  4. Check the reported line number and expected-vs-actual whitespace shown in brackets in the message

Example fix

# before: marker indented 2 spaces, content line uses a tab
  respond <<HTML
	<p>hi</p>
  HTML
# after
  respond <<HTML
  	<p>hi</p>  <- replace tab with 2+ spaces matching marker padding
  HTML
Defensive patterns

Strategy: validation

Validate before calling

// before adapt: verify every heredoc content line starts with the closing marker's padding
func checkPadding(content, marker string) error {
    pad := marker[:len(marker)-len(strings.TrimLeft(marker, " \t"))]
    for i, ln := range strings.Split(content, "\n") {
        if ln != "" && !strings.HasPrefix(ln, pad) {
            return fmt.Errorf("line %d lacks padding %q", i+1, pad)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: Closing marker indented (e.g. two spaces before END) while some content line uses a tab, fewer spaces, or different whitespace than the marker's indentation. strings.Index(lineText, paddingToStrip) != 0.

Common situations: Mixed tabs and spaces from copy-pasting; editor auto-indent changing one line; a content line at lower indentation than the closing marker.

Related errors


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