hashicorp/terraform · error
Can't serialize backend configuration as JSON
Error message
Can't serialize backend configuration as JSON: %s
What it means
After locking, Terraform serializes the chosen backend config into the cache file via s.Backend.SetConfig(configVal, b.ConfigSchema()). The wrapped %s is the JSON marshal/encode error. This indicates the config value (cty) could not be encoded against the backend's declared schema, normally a schema/cty mismatch rather than a user input error.
Solutions
- Read the inner %s to identify which attribute/type failed to encode.
- Update the backend (built-in or plugin) to a version whose schema matches this Terraform build.
- Simplify the backend block to only documented, schema-declared attributes and remove experimental/extra fields.
- If reproducible with a stock backend on the latest release, file a bug with the inner error and the backend type/version.
Example fix
// before
backend "foo" {
unknown_attr = "x" # not in backend schema -> encode mismatch
}
// after
backend "foo" {
# only schema-declared attributes
region = "us-east-1"
} Defensive patterns
Strategy: validation
Validate before calling
// Ensure configVal matches the backend schema before SetConfig.
func validateConfigAgainstSchema(configVal cty.Value, schema *configschema.Block) error {
if schema == nil { return fmt.Errorf("backend schema is nil") }
// use cty/json transform to attempt encode; surface mismatch early
if _, err := json.Marshal(schema.ImpliedType().Value(configVal)); err != nil {
return fmt.Errorf("config does not match backend schema: %w", err)
}
return nil
} Try / catch
if err := s.Backend.SetConfig(configVal, b.ConfigSchema()); err != nil {
if isJSONEncodingErr(err) {
diags = diags.Append(fmt.Errorf("backend config does not match schema (remove undocumented attrs / align backend version): %s", err))
} else {
diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err))
}
return nil, diags
} Prevention
- Use only attributes declared by the backend's documentation/schema.
- Keep the Terraform/OpenTofu CLI version aligned with the backend/plugin version.
- After upgrading a backend plugin, re-init and review schema changes.
When it happens
Trigger: s.Backend.SetConfig fails marshalling configVal to JSON per b.ConfigSchema(). Triggers: backend ConfigSchema is nil or malformed, configVal contains a type the JSON encoder rejects (e.g. an unhandled cty capsule type), or a version skew between the running Terraform and the backend implementation's schema.
Common situations: Using a third-party backend plugin whose schema is inconsistent with the config value produced; a Terraform build mismatch; very rarely, a config value with deeply nested/optional attributes the schema did not declare.
Related errors
- Failed to set state store configuration
- Can't serialize backend configuration as JSON
- credentials file has invalid value for "credentials"…
- Failed to set state store provider configuration
- argument is required
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/f6eb9c3356be11c8.
Report an issue: GitHub.
Appendix: source
Thrown at internal/command/meta_backend.go:1795
if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
return nil, diags
}
defer stateLocker.Unlock()
}
// Store the metadata in our saved state location
s := sMgr.State()
if s == nil {
s = workdir.NewBackendStateFile()
}
s.Backend = &workdir.BackendConfigState{
Type: c.Type,
Hash: uint64(cHash),
}
err := s.Backend.SetConfig(configVal, b.ConfigSchema())
if err != nil {
diags = diags.Append(fmt.Errorf("Can't serialize backend configuration as JSON: %s", err))
return nil, diags
}
// Verify that selected workspace exists in the backend.
if opts.Init && b != nil {
err := m.selectWorkspace(b)
if err != nil {
diags = diags.Append(err)
// FIXME: A compatibility oddity with the 'remote' backend.
// As an awkward legacy UX, when the remote backend is configured and there
// are no workspaces, the output to the user saying that there are none and
// the user should create one with 'workspace new' takes the form of an
// error message - even though it's happy path, expected behavior.
//
// Therefore, only return nil with errored diags for everything else, and
// allow the remote backend to continue and write its configuration to state
// even though no workspace is selected.View on GitHub (pinned to d32a084675)