hashicorp/terraform · error

Can't serialize backend configuration as JSON

Error message

Can't serialize backend configuration as JSON: %s

What it means

Thrown during state migration when bsf.Backend.SetConfig(dstConfig, dstB.ConfigSchema()) fails. The destination backend's config schema cannot serialize the provided hcl config value into the workdir.BackendConfigState JSON form. The error uses %s (not %w) so the underlying error chain is flattened to a string.

Solutions

  1. Read the wrapped err string for the schema validation reason.
  2. Compare your destination backend block against the backend's documented required attributes.
  3. Validate the configuration statically first: `terraform validate` after `terraform init -backend=false`.
  4. Remove any undefined variable references from the backend block; backend blocks cannot use locals or undefined vars.

Example fix

// before
 terraform {
   backend "s3" {
     bucket = "my-bucket"
   }
 }

// after
 terraform {
   backend "s3" {
     bucket = "my-bucket"
     region = "us-east-1"
     key    = "prod/terraform.tfstate"
   }
 }
Defensive patterns

Strategy: validation

Validate before calling

// Validate the destination backend block schema before migration.
cfg, diags := c.loadBackendConfig(rootMod)
if diags.HasErrors() { return diags }
if err := bsf.Backend.SetConfig(cfg, dstB.ConfigSchema()); err != nil {
    return fmt.Errorf("destination backend config is invalid for type %q: %w", dstB.Type(), err)
}

Try / catch

err := bsf.Backend.SetConfig(dstConfig, dstB.ConfigSchema())
if err != nil {
    diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err))
    view.Diagnostics(diags)
    hintInvalidBackendBlock(view, dstB.Type())
    return 1
}

Prevention

When it happens

Trigger: Destination backend schema rejects the config value: required attribute missing, type mismatch (string vs list), unknown hcl value remaining after no variable evaluation, or the schema returned by ConfigSchema() is empty/malformed.

Common situations: Migrating from local to S3 backend with a backend block missing required fields (e.g. `region`, `bucket`); typo in backend attribute name; variables in backend config that are not defined; custom backend with incomplete schema.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/9ff16ebf82069d15. Report an issue: GitHub.

Appendix: source

Thrown at internal/command/state_migrate.go:197

			migrateOpts.DestinationType = rootMod.Backend.Type
			migrateOpts.Destination = dstB

			// Capture details of the destination backend for updating the backend state file after a successful migration.
			_, cHash, bcDiags := c.backendConfig(&BackendOpts{
				BackendConfig: rootMod.Backend,
			})
			diags = diags.Append(bcDiags)
			if bcDiags.HasErrors() {
				view.Diagnostics(diags)
				return 1
			}
			bsf.Backend = &workdir.BackendConfigState{
				Type: rootMod.Backend.Type,
				Hash: uint64(cHash),
			}
			err := bsf.Backend.SetConfig(dstConfig, dstB.ConfigSchema())
			if err != nil {
				diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err))
				view.Diagnostics(diags)
				return 1
			}
		}
	} else if rootMod.StateStore != nil {
		// Get single required_providers entry for state store provider.
		dstReq, dstReqDiags := c.getDestinationStateStoreProviderRequirements(rootMod.StateStore.ProviderAddr, rootMod.ProviderRequirements)
		diags = diags.Append(dstReqDiags)
		if dstReqDiags.HasErrors() {
			view.Diagnostics(diags)
			return 1
		}

		// Load any pre-existing destination provider lock file.
		var lockfilePath string
		if args.DestinationLockFilePath != "" {
			lockfilePath = args.DestinationLockFilePath
		} else {

View on GitHub (pinned to d32a084675)