cloudflare/cloudflared · error

Unable to marshal tunnel credentials to JSON

Error message

Unable to marshal tunnel credentials to JSON

What it means

writeTunnelCredentials marshals the tunnel's credentials struct to JSON before writing it to <tunnelID>.json with 0400 permissions. json.Marshal on this struct should never fail in practice, so this error indicates an internal serialization problem (e.g. an unsupported or corrupt field value in the credentials object).

Source

Thrown at cmd/cloudflared/tunnel/subcommands.go:310

func tunnelFilePath(tunnelID uuid.UUID, directory string) (string, error) {
	fileName := fmt.Sprintf("%v.json", tunnelID)
	filePath := filepath.Clean(fmt.Sprintf("%s/%s", directory, fileName))
	return homedir.Expand(filePath)
}

// writeTunnelCredentials saves `credentials` as a JSON into `filePath`, only if
// the file does not exist already
func writeTunnelCredentials(filePath string, credentials *connection.Credentials) error {
	if _, err := os.Stat(filePath); !os.IsNotExist(err) {
		if err == nil {
			return fmt.Errorf("%s already exists", filePath)
		}
		return err
	}
	body, err := json.Marshal(credentials)
	if err != nil {
		return errors.Wrap(err, "Unable to marshal tunnel credentials to JSON")
	}
	return os.WriteFile(filePath, body, 0400)
}

func buildListCommand() *cli.Command {
	return &cli.Command{
		Name:        "list",
		Action:      cliutil.ConfiguredAction(listCommand),
		Usage:       "List existing tunnels",
		UsageText:   "cloudflared tunnel [tunnel command options] list [subcommand options]",
		Description: "cloudflared tunnel list will display all active tunnels, their created time and associated connections. Use -d flag to include deleted tunnels. See the list of options to filter the list",
		Flags: []cli.Flag{
			outputFormatFlag,
			showDeletedFlag,
			listNameFlag,
			listNamePrefixFlag,
			listExcludeNamePrefixFlag,
			listExistedAtFlag,

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Read the wrapped json.Marshal error for the exact field that failed to serialize
  2. Update cloudflared to the latest release; this indicates a bug in the version you are running
  3. If running a custom patch, remove non-JSON-serializable fields from the tunnel credentials struct
  4. Retry the create/token command to rule out transient corrupted state

Example fix

// before (custom build)
type Credentials struct { Conn func() } // unsupported type
// after
type Credentials struct { AccountTag, TunnelSecret, TunnelID string }
Defensive patterns

Strategy: type-guard

Validate before calling

func credentialsSerializable(c Credentials) error {
	v := reflect.ValueOf(c)
	for i := 0; i < v.NumField(); i++ {
		if !v.Field(i).CanInterface() { continue }
		switch v.Field(i).Kind() {
		case reflect.Chan, reflect.Func, reflect.UnsafePointer:
			return fmt.Errorf("field %s not JSON-serializable", v.Type().Field(i).Name)
		}
	}
	return nil
}

Type guard

func isMarshalable(v any) bool {
	_, err := json.Marshal(v)
	return err == nil
}

Try / catch

body, err := json.Marshal(credentials)
if err != nil {
	return fmt.Errorf("Unable to marshal tunnel credentials to JSON: %w", err)
}

Prevention

When it happens

Trigger: Called from create and tokenCommand when the Credentials struct returned by the tunnel-creation API or token parsing contains a value Go's encoding/json cannot marshal (nominally only for unsupported types like channels, funcs, or cyclic data).

Common situations: Practically rare — it can surface after code modifications introducing a non-serializable field to the credentials struct, or from corrupted in-memory credential state in custom builds/patches of cloudflared.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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