TerraformXVIII · Troubleshooting and RecoveryProduction Terraform
Terraform Logging and Debugging
What you'll learn
- Set the appropriate Terraform log verbosity for a given failure scenario
- Use TF_LOG_PATH without losing the distinction between core and provider logs
- Separate Terraform Core and provider-plugin evidence with TF_LOG_CORE and TF_LOG_PROVIDER
- Recognise sensitive values in logs and apply storage, access, and retention controls
- Capture a bounded, useful debug log without exposing credentials or flooding storage
Prerequisites
None — start here.
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13
Terraform logging is the controlled release of diagnostic evidence. In
Terraform 1.9.x, TF_LOG enables detailed output on stderr, while
TF_LOG_CORE and TF_LOG_PROVIDER let you narrow the evidence to
Terraform Core or provider plugins. TF_LOG_PATH sends enabled logs
to a file, but it does not enable logging by itself. Logging is most
useful when the verbosity matches the question being asked.
The levels are TRACE, DEBUG, INFO, WARN, and ERROR, in
descending verbosity. The right level is the lowest one that answers
the question. TRACE is not a routine production setting; it can
produce a very large stream and can expose values that the normal
Terraform presentation would hide.
What each control does
TF_LOG: choose the verbosity
Set TF_LOG for the current command so the setting cannot silently
affect the next engineer’s shell.
READ-ONLY — the following plan inspects the working directory and does not apply infrastructure.
TF_LOG=INFO terraform plan -input=false -no-color
The useful levels are:
ERROR. The command failed and the final error is the main evidence. Use this for routine automation or a compact failure report.WARN. Show recoverable anomalies and configuration warnings without the full provider trace.INFO. Show high-level lifecycle events such as provider configuration, state operations, and resource actions.DEBUG. Investigate provider calls, resource planning decisions, graph operations, and most apply failures.TRACE. Use for a narrow, unexplained boundary or a suspected Terraform bug. Keep the run short and capture only the relevant command.
Terraform 1.9.x also accepts JSON for machine-oriented output. It
is not a stable parsing contract across minor releases, so do not
build a permanent parser around it without a compatibility plan.
TF_LOG_PATH: write the stream to one place
TF_LOG_PATH redirects enabled logging to a file. Set a restrictive
umask and confirm the path is not a shared directory or a CI workspace
that other jobs can read.
CONFIGURATION — writes a diagnostic file. It does not apply infrastructure, but the file may contain sensitive data.
umask 077
TF_LOG=DEBUG TF_LOG_PATH=/tmp/terraform-apply.log \
terraform plan -input=false -no-color
A compact filtered view is useful, but filtering is a local investigation step, not the only retained evidence. Keep the original protected until the incident record is complete.
TF_LOG_CORE and TF_LOG_PROVIDER: separate the boundaries
Terraform Core builds the graph and coordinates state. Provider
plugins perform provider-specific planning and API operations. The
two variables accept the same level names as TF_LOG and activate only
the relevant subset.
CONFIGURATION — enables bounded provider logging and writes a file. The command still plans only.
TF_LOG_CORE=INFO TF_LOG_PROVIDER=DEBUG \
TF_LOG_PATH=/tmp/provider-only.log \
terraform plan -input=false -no-color
Use TF_LOG_PROVIDER=DEBUG when a provider rejects a resource or when
the API operation is unclear. Use TF_LOG_CORE=DEBUG when the graph,
state serialisation, or Terraform orchestration is the suspected
boundary. Do not assume that a provider name in a message proves a
provider bug; first check the API response and the resource input.
The right verbosity by scenario
| Scenario | Starting level | Scope | Stop condition |
|---|---|---|---|
| Routine plan or apply | No TF_LOG | Normal output | Only enable if output lacks the needed evidence |
| High-level operational check | INFO | Core and provider lifecycle | Stop when the affected stage is located |
| Provider API or schema error | DEBUG | TF_LOG_PROVIDER=DEBUG, TF_LOG_CORE=INFO | One resource or one API call is understood |
| Graph or state orchestration error | DEBUG | TF_LOG_CORE=DEBUG, TF_LOG_PROVIDER=ERROR | The graph or state event is identified |
| Single unexplained boundary | TRACE | Narrow command and protected file | The required evidence is captured; never run a whole estate at trace by default |
| Suspected Terraform bug | TRACE plus provider debug | Smallest reproducible configuration | Preserve the version, exact command, and sanitised log |
A real provider failure may produce entries like this illustrative output:
2026/08/13 02:17:44 [INFO] provider: configuring client
2026/08/13 02:17:45 [DEBUG] provider.terraform.io/hashicorp/aws: creating resource
2026/08/13 02:17:46 [ERROR] provider.terraform.io/hashicorp/aws: operation error
2026/08/13 02:17:46 [ERROR] Retry limit reached
The sequence tells you to inspect the provider’s request, retry budget, and service status. It does not justify exposing the complete log to a public channel.
Redaction is not automatic
A value marked sensitive in HCL is a presentation control. It is deliberately suppressed in normal plan and apply output, but it is not a promise that every provider SDK, provider diagnostic, state JSON, or debug log redacts it.
output "database_password" {
value = var.database_password
sensitive = true
}
terraform output will hide the value by default, but the value can
still exist in state if the resource stores it and can appear in a
provider request or a debug stream. Treat the variable, state, saved
plan, and log as sensitive until proven otherwise.
Five logging failure modes
1. Too much logging by default
Observable symptom. The CI job is slow, the log store grows from a few megabytes to gigabytes, and provider retries dominate the output. The useful error is buried under routine lifecycle events.
Recovery. Stop the run, use INFO or WARN for the next bounded
reproduction, and capture only the affected address or command. Keep
trace logging as an explicit incident action with a deletion time.
2. Too little logging for a transient API failure
Observable symptom. The apply fails with a generic timeout and no provider operation, request identifier, or retry count. Re-running at the same verbosity produces no new evidence.
Recovery. Use TF_LOG_PROVIDER=DEBUG, TF_LOG_CORE=INFO, and a
protected TF_LOG_PATH on one failed resource. If the failure is still
opaque, add a second run with TF_LOG_PROVIDER=TRACE; do not run the
whole estate at trace.
3. Log path exists but no log appears
Observable symptom. TF_LOG_PATH is set, but the file is absent or
stays empty.
Recovery. Confirm that TF_LOG, TF_LOG_CORE, or TF_LOG_PROVIDER
is also set, check the path and directory permissions, and inspect the
process exit code. A path variable stores output; it does not select a
verbosity.
4. Core and provider evidence are mixed together
Observable symptom. The log is full of graph events but has no provider operation, or it is full of SDK details but does not show the state action that led to the call.
Recovery. Split the runs. Use TF_LOG_CORE=DEBUG for the graph and
TF_LOG_PROVIDER=DEBUG for provider API evidence. Correlate the two
streams by command time, resource address, and provider request ID.
5. A log leaks a secret or survives too long
Observable symptom. Search or retention audit finds a token,
password, private address, or customer value in a log artefact. The
incident record says the value was sensitive = true in HCL.
Recovery. Revoke and rotate the credential, remove the local file, restrict the archive, and review who accessed it. Use an allow-listed log collector with encryption and bounded retention for future runs.
Security, performance, and production guidance
Trace logging increases CPU, disk, network, and log-processing work.
It can also disable any practical human review of a large apply. Use
INFO for operational visibility, DEBUG for a bounded provider or
graph diagnosis, and TRACE only when the other levels have failed to
answer the question.
Keep the Terraform version, provider lock selections, command,
workspace, and resource address in the incident record. Redact at the
source where possible, then apply access control and retention to the
artefact. Do not put a full log in a pull request, chat transcript, or
unrestricted ticket. The absence of a secret from terraform plan is
not evidence that it is absent from TF_LOG=TRACE.
Verification
- You can set
TF_LOGto a level that matches the failure rather than always using trace. - You can explain why
TF_LOG_PATHmust be paired with an enabled logging variable. - You can isolate Core and provider-plugin evidence with
TF_LOG_COREandTF_LOG_PROVIDER. - You can identify the limits of
sensitive = truein logs and JSON artefacts. - You can redact, restrict, rotate, and delete a diagnostic log when it exposes sensitive data.
- You can cap verbosity so a diagnosis does not flood CI or fill the incident runner’s storage.
Knowledge check · 7 questions
Q1. Which setting enables the most verbose Terraform Core and provider logging for a bounded run?
Q2. What must be true when using `TF_LOG_PATH`?
Q3. Terraform debug and trace logs can contain sensitive values even when a variable is marked `sensitive = true` in HCL.
Q4. Which logging choices are appropriate for a narrow provider diagnosis? (Select all that apply.)
Q5. A plan succeeds at `INFO`, but a provider rejects one resource with no request details. What is the best next logging run?
Q6. Which log level is the routine starting point for a high-level operational check?
Q7. Why pair `TF_LOG_CORE=INFO` with `TF_LOG_PROVIDER=DEBUG`?
Passing score: 75%. Answers are checked in this browser.