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
- Make every content line's leading whitespace use the same characters and at least the depth of the closing marker's indentation
- Avoid mixing tabs and spaces inside heredocs
- Simplest fix: put the closing marker at column 0 and left-align content, or indent both consistently
- 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
- Use spaces only inside heredocs, no tabs
- Indent content at least as far as the closing marker
- Run `caddy adapt` after re-indenting heredoc blocks
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
- incomplete heredoc <<%s on line #%d, expected ending marker
- missing opening heredoc marker on line #%d; must contain onl
- too many '<' for heredoc on line #%d; only use two, for exam
- heredoc marker on line #%d must contain only alphanumeric ch
- invalid action type
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/09fb6372371e083d.
Report an issue: GitHub.