Skip to content
Infrastructure as Code iac variables 4 min read

Sensitive Values

Infrastructure configurations inevitably touch secrets: database passwords, API tokens, TLS private keys, and connection strings. Terraform’s sensitive = true flag tells the CLI to redact a value from the human-readable plan and apply output so it never leaks into your terminal scrollback, CI logs, or pull request comments. It’s a guardrail against accidental disclosure — not encryption — and understanding exactly what it does (and doesn’t) protect is essential to handling secrets responsibly.

Marking a variable sensitive

Add sensitive = true to an input variable to suppress its value everywhere Terraform would normally echo it. This is most useful for credentials passed in from a .tfvars file, an environment variable, or a CI secret store.

variable "db_password" {
  type        = string
  description = "Master password for the RDS instance"
  sensitive   = true
}

resource "aws_db_instance" "main" {
  identifier          = "app-db"
  engine              = "postgres"
  engine_version      = "16.3"
  instance_class      = "db.t3.micro"
  allocated_storage   = 20
  username            = "appuser"
  password            = var.db_password
  skip_final_snapshot = true
}

When a sensitive value flows into a resource attribute, Terraform automatically marks the dependent display as sensitive too. The plan shows the change without revealing the secret:

Output:

  # aws_db_instance.main will be created
  + resource "aws_db_instance" "main" {
      + identifier        = "app-db"
      + engine            = "postgres"
      + password          = (sensitive value)
      + username          = "appuser"
      ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Sensitivity is contagious. If you interpolate a sensitive value into a string, build it into a map, or pass it through a local, the resulting expression also becomes sensitive and is redacted. This prevents leaks through indirect references.

Marking an output sensitive

Outputs are printed in full after every apply and are queryable with terraform output, so any output derived from a secret must be flagged. Terraform will actually error if you try to surface a sensitive value through a non-sensitive output.

output "db_connection_string" {
  description = "Postgres connection URI"
  value       = "postgres://appuser:${var.db_password}@${aws_db_instance.main.endpoint}/app"
  sensitive   = true
}

Without sensitive = true here, you’d get an error like Output refers to sensitive values. With it, the apply output redacts the value:

Output:

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

db_connection_string = <sensitive>

To deliberately read a redacted output — for example, to pipe it into another tool — pass the -raw flag (single value) or -json:

terraform output -raw db_connection_string

Both terraform output -raw and terraform output -json print the plaintext secret. Never run these in a shared terminal or pipe them into a command that logs its arguments.

The state file caveat

The single most important thing to understand: sensitive = true only affects display. The actual value is still written, in cleartext, to the Terraform state file (terraform.tfstate). Anyone with read access to your state can recover every secret.

terraform state pull | jq '.resources[].instances[].attributes.password'

This means securing the state backend is non-negotiable when secrets are involved.

ConcernWhat sensitive = true doesWhat you still must do
CLI / CI log leakageRedacts plan, apply, and output textAvoid output -raw in shared logs
State at restNothing — stored in cleartextUse an encrypted backend (S3 + SSE/KMS)
State access controlNothingLock down backend IAM / RBAC
Secret rotationNothingUse a dedicated secrets manager

This behavior is identical in OpenTofu, which is a drop-in replacement for the Terraform CLI. OpenTofu additionally offers built-in state encryption, letting you encrypt the state file itself with a passphrase or KMS key — a meaningful upgrade over Terraform’s reliance on backend-level encryption alone.

Handling secrets properly

The recommended pattern is to keep secrets out of Terraform configuration entirely and fetch them at apply time from a managed store. AWS Secrets Manager, HashiCorp Vault, and SSM Parameter Store all expose data sources for this.

data "aws_secretsmanager_secret_version" "db" {
  secret_id = "prod/app/db-password"
}

resource "aws_db_instance" "main" {
  identifier          = "app-db"
  engine              = "postgres"
  engine_version      = "16.3"
  instance_class      = "db.t3.micro"
  allocated_storage   = 20
  username            = "appuser"
  password            = data.aws_secretsmanager_secret_version.db.secret_string
  skip_final_snapshot = true
}

Values read from aws_secretsmanager_secret_version are automatically treated as sensitive, so they’re redacted in plan output without any extra annotation. The secret still lands in state, but it never lives in your repository or .tfvars files — and rotation happens in the secrets manager, not in your Terraform code.

You can also wrap an arbitrary expression with the sensitive() function to mark it sensitive on the fly, or use nonsensitive() to deliberately strip the flag when you’re certain a value is safe to display.

Best practices

  • Mark every variable and output that carries a credential, token, or key with sensitive = true so it can’t leak through logs.
  • Treat state as a secret: use a remote backend with encryption at rest (S3 + KMS) and tight IAM, and consider OpenTofu’s state encryption for defense in depth.
  • Prefer fetching secrets from a manager (Secrets Manager, Vault, SSM) over storing them in .tfvars or the repo.
  • Never commit .tfvars files containing secrets — add them to .gitignore and pass secrets via environment variables or CI secret stores.
  • Reserve terraform output -raw and -json for trusted, non-logged contexts; assume their output is plaintext.
  • Use nonsensitive() sparingly and only on values you have verified are safe to expose.
Last updated June 14, 2026
Was this helpful?