caddyserver/caddy · error

parsing %s: %v

Error message

parsing %s: %v

What it means

Returned by the templates module's httpInclude function when the internal recursion-prevention header (X-Caddy-Recursion-Prevention, or similar constant) is present on the incoming request but its value is not a valid integer. The header is normally set by Caddy itself to count virtual-request nesting depth.

Source

Thrown at modules/caddyhttp/templates/tplcontext.go:180

	if err != nil {
		return err
	}

	return nil
}

// funcHTTPInclude returns the body of a virtual (lightweight) request
// to the given URI on the same server. Note that included bodies
// are NOT escaped, so you should only include trusted resources.
// If it is not trusted, be sure to use escaping functions yourself.
func (c TemplateContext) funcHTTPInclude(uri string) (string, error) {
	// prevent virtual request loops by counting how many levels
	// deep we are; and if we get too deep, return an error
	recursionCount := 1
	if numStr := c.Req.Header.Get(recursionPreventionHeader); numStr != "" {
		num, err := strconv.Atoi(numStr)
		if err != nil {
			return "", fmt.Errorf("parsing %s: %v", recursionPreventionHeader, err)
		}
		if num >= 3 {
			return "", fmt.Errorf("virtual request cycle")
		}
		recursionCount = num + 1
	}

	buf := bufPool.Get().(*bytes.Buffer)
	buf.Reset()
	defer bufPool.Put(buf)

	virtReq, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return "", err
	}
	virtReq.Host = c.Req.Host
	virtReq.RemoteAddr = "127.0.0.1:10000" // https://github.com/caddyserver/caddy/issues/5835
	virtReq.Header = c.Req.Header.Clone()

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Find who sends the recursion header with a bogus value and stop it (do not forward that header from outside)
  2. Strip the header at the trust boundary: request_header -X-Caddy-Recursion-Prevention (or via reverse_proxy headers)
  3. Retry the request without the header to confirm the template works
  4. If you control the template, guard httpInclude calls behind trusted-input checks

Example fix

# before
localhost {
    templates
    reverse_proxy backend:8080  # backend echoes all headers back with junk value
}

# after
localhost {
    templates
    @junk header X-Caddy-Recursion-Prevention *
    request_header @junk -X-Caddy-Recursion-Prevention
    reverse_proxy backend:8080
}
Defensive patterns

Strategy: validation

Validate before calling

# strip client-supplied recursion header at the edge
@bad header X-Caddy-Recursion-Prevention *
request_header @bad -X-Caddy-Recursion-Prevention

Prevention

When it happens

Trigger: A client (or an upstream proxy that forwards arbitrary headers) sends the request with the recursion-prevention header set to a non-numeric value like 'abc' or '2.5', and a template then calls {{ httpInclude "/path" }}.

Common situations: Misconfigured reverse proxy forwarding internal Caddy headers from the outside, security scanners sending junk headers, or curl commands copied from debug traces that include internal headers.

Related errors


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