cloudflare/cloudflared · error

failed to unmarshal quick Tunnel

Error message

failed to unmarshal quick Tunnel

What it means

The quick-tunnel service returned a body that could not be parsed as JSON (QuickTunnelResponse). cloudflared logs the raw body and status, then returns this wrapped error. It means the API replied something other than the expected JSON schema.

Source

Thrown at cmd/cloudflared/tunnel/quick_tunnel.go:96

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return errors.Wrap(err, "failed to read quick-tunnel response")
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		var data QuickTunnelResponse
		if err := json.Unmarshal(respBody, &data); err == nil && len(data.Errors) > 0 {
			return fmt.Errorf("quick tunnel provisioning failed with status %d: %s", resp.StatusCode, formatQuickTunnelErrors(data.Errors))
		}
		return fmt.Errorf("quick tunnel provisioning failed with status %d: %s", resp.StatusCode, string(respBody))
	}

	var data QuickTunnelResponse
	if err := json.Unmarshal(respBody, &data); err != nil {
		respString := string(respBody)
		fields := map[string]interface{}{"status_code": resp.Status}
		sc.log.Err(err).Fields(fields).Msgf("Error unmarshaling QuickTunnel response: %s", respString)
		return errors.Wrap(err, "failed to unmarshal quick Tunnel")
	}

	// TODO(TUN-10791): Add CLI-level coverage that provisioning errors are logged to users.
	if len(data.Errors) > 0 {
		return fmt.Errorf("quick tunnel provisioning failed: %s", formatQuickTunnelErrors(data.Errors))
	}

	if !data.Success {
		return errors.New("quick tunnel provisioning failed")
	}

	tunnelID, err := uuid.Parse(data.Result.ID)
	if err != nil {
		return errors.Wrap(err, "failed to parse quick Tunnel ID")
	}

	credentials := connection.Credentials{
		AccountTag:   data.Result.AccountTag,

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Check the logged 'Error unmarshaling QuickTunnel response' line to see the raw body (often an HTML captive-portal page)
  2. Complete captive-portal/Wi-Fi login or bypass the intercepting proxy and retry
  3. Upgrade cloudflared to the latest version to match current API response schema
  4. If using a custom quick-service, verify it returns the Cloudflare QuickTunnelResponse JSON shape
Defensive patterns

Strategy: validation

Validate before calling

ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
    return fmt.Errorf("quick-tunnel returned non-JSON content type %q (captive portal/proxy?)", ct)
}

Try / catch

if err := json.Unmarshal(respBody, &data); err != nil {
    log.Printf("quick-tunnel non-JSON response (%d): %s", resp.StatusCode, string(respBody))
    return err
}

Prevention

When it happens

Trigger: json.Unmarshal(respBody, &data) fails after a successful HTTP response from the quick-service endpoint.

Common situations: Captive portals or proxies returning HTML error/login pages instead of JSON; an outdated cloudflared talking to a changed API; custom quick-service endpoints that return different payloads.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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