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
- Find who sends the recursion header with a bogus value and stop it (do not forward that header from outside)
- Strip the header at the trust boundary: request_header -X-Caddy-Recursion-Prevention (or via reverse_proxy headers)
- Retry the request without the header to confirm the template works
- 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
- Do not forward internal X-Caddy-* headers through proxies or load tests
- Strip unknown client headers at the trust boundary
- When replaying captured requests, remove internal headers first
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
- virtual request cycle
- server %s: %v
- unsupported map key type in header match: %T
- %v: the default header modifier ('?') can only be used on re
- replacement %d for header field '%s': %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/29a26bd7e5c51856.
Report an issue: GitHub.