caddyserver/caddy · error

incomplete heredoc <<%s on line #%d, expected ending marker

Error message

incomplete heredoc <<%s on line #%d, expected ending marker %s

What it means

While lexing a heredoc (<<END ... END), the lexer reached end-of-input before seeing the closing marker line. The message names the marker and the line where the heredoc started, so you can find the unterminated block.

Source

Thrown at caddyconfig/caddyfile/lexer.go:128

	var heredocMarker string

	makeToken := func(quoted rune) bool {
		l.token.Text = string(val)
		l.token.wasQuoted = quoted
		l.token.heredocMarker = heredocMarker
		return true
	}

	for {
		// Read a character in; if err then if we had
		// read some characters, make a token. If we
		// reached EOF, then no more tokens to read.
		// If no EOF, then we had a problem.
		ch, _, err := l.reader.ReadRune()
		if err != nil {
			if len(val) > 0 {
				if inHeredoc {
					return false, fmt.Errorf("incomplete heredoc <<%s on line #%d, expected ending marker %s", heredocMarker, l.line+l.skippedLines, heredocMarker)
				}

				return makeToken(0), nil
			}
			if err == io.EOF {
				return false, nil
			}
			return false, err
		}

		// detect whether we have the start of a heredoc
		if (!quoted && !btQuoted) && (!inHeredoc && !heredocEscaped) &&
			len(val) > 1 && string(val[:2]) == "<<" {
			// a space means it's just a regular token and not a heredoc
			if ch == ' ' {
				return makeToken(0), nil
			}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Add the closing marker on its own line, exactly matching the opening marker, no leading/trailing whitespace
  2. Check for trailing spaces or CR characters on the closing marker line (cat -A)
  3. Confirm the marker only uses alphanumerics, dashes, underscores
  4. Ensure the file ends with a newline after the marker

Example fix

# before
respond <<HTML
  <p>hi</p>
# (EOF, no closing marker)
# after
respond <<HTML
  <p>hi</p>
HTML
Defensive patterns

Strategy: validation

Validate before calling

// before adapt: every '<<MARKER' opener must have a line equal to MARKER
type validator struct{ marker string; closed bool }
func checkHeredocs(lines []string) error {
    var open string
    for _, ln := range lines {
        t := strings.TrimSpace(ln)
        if open == "" {
            if strings.HasPrefix(t, "<<") { open = strings.TrimPrefix(t, "<<") }
        } else if t == open {
            open = ""
        }
    }
    if open != "" { return fmt.Errorf("heredoc %s never closed", open) }
    return nil
}

Prevention

When it happens

Trigger: A Caddyfile contains <<MARKER but no subsequent line equal to MARKER before EOF. This can be a missing closing marker, a marker with trailing characters/spaces, or CR/LF mismatches making the closing line not match exactly.

Common situations: Copy-pasting heredoc config (e.g. inline HTML or TLS policies) and dropping the last line; using an editor that trims trailing whitespace on the closing marker; Windows line endings where the marker line has trailing '\r'.

Related errors


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