cloudflare/cloudflared · error

header Key: %s malformed

Error message

header Key: %s malformed

What it means

buildHTTPRequest translates edge request metadata into HTTP headers on the outgoing origin request. HTTP header metadata is encoded as metadata keys of the form "httpHeaderKey:<HTTPHeader>"; when such a key does not split into exactly two colon-separated parts, the code rejects it with "header Key: %s malformed". This prevents silently dropping or mis-assigning header values on proxied requests.

Source

Thrown at connection/quic_connection.go:364

) (*tracing.TracedHTTPRequest, error) {
	metadata := connectRequest.MetadataMap()
	dest := connectRequest.Dest
	method := metadata[HTTPMethodKey]
	host := metadata[HTTPHostKey]
	isWebsocket := connectRequest.Type == pogs.ConnectionTypeWebsocket

	req, err := http.NewRequestWithContext(ctx, method, dest, body)
	if err != nil {
		return nil, err
	}

	req.Host = host
	for _, metadata := range connectRequest.Metadata {
		if strings.Contains(metadata.Key, HTTPHeaderKey) {
			// metadata.Key is off the format httpHeaderKey:<HTTPHeader>
			httpHeaderKey := strings.Split(metadata.Key, ":")
			if len(httpHeaderKey) != 2 {
				return nil, fmt.Errorf("header Key: %s malformed", metadata.Key)
			}
			req.Header.Add(httpHeaderKey[1], metadata.Val)
		}
	}
	// Go's http.Client automatically sends chunked request body if this value is not set on the
	// *http.Request struct regardless of header:
	// https://go.googlesource.com/go/+/go1.8rc2/src/net/http/transfer.go#154.
	if err := setContentLength(req); err != nil {
		return nil, fmt.Errorf("Error setting content-length: %w", err)
	}

	// Go's client defaults to chunked encoding after a 200ms delay if the following cases are true:
	//   * the request body blocks
	//   * the content length is not set (or set to -1)
	//   * the method doesn't usually have a body (GET, HEAD, DELETE, ...)
	//   * there is no transfer-encoding=chunked already set.
	// So, if transfer cannot be chunked and content length is 0, we dont set a request body.
	if !isWebsocket && !isTransferEncodingChunked(req) && req.ContentLength == 0 {

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Fix the metadata key to have exactly one colon: "httpHeaderKey:My-Header"
  2. Remove extra colons from the header name (header names cannot contain ':')
  3. If you control the sender, validate metadata keys before sending
  4. Check which component emits the metadata (edge config or client) and correct it there

Example fix

// before
httpHeaderKey: "X-Custom:Header"
// after
httpHeaderKey: "X-Custom-Header"
Defensive patterns

Strategy: validation

Validate before calling

// validate httpHeaderKey metadata before sending
key := metadata.Key
if strings.Contains(key, "httpHeaderKey") {
	parts := strings.Split(key, ":")
	if len(parts) != 2 || parts[1] == "" {
		return fmt.Errorf("invalid httpHeaderKey metadata: %q", key)
	}
}

Prevention

When it happens

Trigger: A connect request carries metadata whose key contains "httpHeaderKey" but is not of the exact form "httpHeaderKey:HeaderName" — e.g. "httpHeaderKey" with no colon, or "httpHeaderKey:X:Y" with an extra colon.

Common situations: Misconfigured upstream clients injecting header pass-through metadata; edge configs or ingress rules authored with the wrong delimiter; typos in custom header propagation rules.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/f62877aea3756bc3. Report an issue: GitHub.