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 -rawandterraform output -jsonprint 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.
| Concern | What sensitive = true does | What you still must do |
|---|---|---|
| CLI / CI log leakage | Redacts plan, apply, and output text | Avoid output -raw in shared logs |
| State at rest | Nothing — stored in cleartext | Use an encrypted backend (S3 + SSE/KMS) |
| State access control | Nothing | Lock down backend IAM / RBAC |
| Secret rotation | Nothing | Use 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 = trueso 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
.tfvarsor the repo. - Never commit
.tfvarsfiles containing secrets — add them to.gitignoreand pass secrets via environment variables or CI secret stores. - Reserve
terraform output -rawand-jsonfor trusted, non-logged contexts; assume their output is plaintext. - Use
nonsensitive()sparingly and only on values you have verified are safe to expose.