[Virtual Event] IaCConf 2026: Real stories on how infra teams are keeping pace

Register Now ➡️

Terraform

How to Debug & Troubleshoot Terraform Projects: Tutorial

Debug Terraform

Debugging and logging are the lifelines of developers when troubleshooting any issue. If something fails in a production environment, developers always look to the application logs, which can provide the type, cause, and time of the error. 

The exact same principle is also followed when debugging the Terraform script. Terraform projects can grow very quickly when multiple teams work on them. If you do not have a proper logging setup for troubleshooting, the project team will have a very difficult time troubleshooting and debugging.

In this article, we will focus on the commonly followed practices for debugging Terraform Projects. 

How to enable the logs for debugging? How to set the log file for debugging? How to manage log levels? Here’s how.

TL;DR

  • Terraform provides two environment variables for debugging: TF_LOG (sets the verbosity level: TRACE, DEBUG, INFO, WARN, or ERROR) and TF_LOG_PATH (writes logs to a file).
  • For more granular control, use TF_LOG_CORE and TF_LOG_PROVIDER separately (available since Terraform 0.15).
  • To validate variable inputs, use the validation block with a custom error_message.
  • To inspect your infrastructure state, use terraform show and terraform state pull.
  • For quick expression and value testing without touching your infrastructure, use terraform console.

Prerequisite

The only prerequisite we have for this blog post is you must install Terraform on your host machine.

You can verify your Terraform installation by running the following Terraform version command:

$ terraform -version
Terraform v1.14.9
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v6.42.0

1. Set Log level using TF_LOG

Terraform provides several log levels for debugging and troubleshooting. As developers, we have to choose and set the log level for our Terraform project.

Types of log level

Terraform provides five log levels, each offering a different degree of detail. You should choose the one that matches how deeply you need to inspect a given issue. Higher verbosity gives more information but produces larger, harder-to-read log files.

  • TRACE — The most verbose level. Logs every action and step Terraform takes, including internal graph operations. Use this as a last resort when no other level gives you enough information.
  • DEBUG — Logs internal operations at a higher level than TRACE. Good for diagnosing complex provider or configuration issues without the full noise of TRACE.
  • INFO — Logs general progress messages about what Terraform is doing. Useful for routine visibility without impacting readability.
  • WARN — Logs non-critical issues that don’t stop execution but may indicate a misconfiguration worth addressing.
  • ERROR — Logs only critical failures that prevent Terraform from continuing. The minimum level for any production logging setup.

How to set the log level to enable debugging?

The log level can be set with the environment variable TF_LOG. You need to export the variable with the correct log level. Here is an example of where TF_LOG is set with DEBUG level:

$ export TF_LOG=”DEBUG”

Verify the log level:

$ echo $TF_LOG
DEBUG

Separating core and provider logs (Terraform 0.15+)

Since Terraform 0.15, you can enable logging separately for Terraform Core and provider plugins using two dedicated environment variables: TF_LOG_CORE and TF_LOG_PROVIDER. Both accept the same log levels as TF_LOG.

This is especially useful when you want verbose provider output (e.g., raw AWS API calls) without the noise of Terraform Core’s internal graph operations, or vice versa.

# Log only Terraform Core at ERROR level, and provider at TRACE level
export TF_LOG_CORE=ERROR
export TF_LOG_PROVIDER=TRACE

Or, if you only care about core internals:

export TF_LOG_CORE=DEBUG
export TF_LOG_PROVIDER=OFF

Note: TF_LOG_CORE and TF_LOG_PROVIDER only take effect when TF_LOG is not set. If TF_LOG is set, it overrides both. To use the granular variables, make sure TF_LOG is unset:

unset TF_LOG
export TF_LOG_PROVIDER=TRACE

2. Set up log file using TF_LOG_PATH

After setting the log level, the next thing we need to set is the Log File Path, and for that we are going to use the environment variable TF_LOG_PATH.

For this tutorial, we are going to create a log file named terraform-debug.log in the current working directory. You can name it and place it anywhere that suits your project structure.

*Note – You can name and choose the log file path as per your requirement.

Let’s export the TF_LOG_PATH

export TF_LOG_PATH="./terraform-debug.log"

3. Verify the logs generated by Terraform

Now, after setting both Log Level and Log File path, let’s create a very small Terraform project to start an AWS EC2 instance.

Here is my main.tf:

terraform {
  required_version = ">= 1.14.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "ec2_example" {
  ami           = "ami-0c02fb55956c7d316" # Amazon Linux 2 (us-east-1) — update for your region
  instance_type = "t2.micro"

  tags = {
    Name = "Test - Terraform EC2"
  }
}

AMI IDs are region-specific and change over time. The value above is an example for us-east-1. Find the current Amazon Linux 2 AMI for your region in the AWS AMI Catalog, or use an aws_ami data source to fetch it dynamically.

Apply the above Terraform configuration by running the following command: 

  1. terraform init
  2. terraform apply

After successful execution of the above Terraform code, let’s verify the debug logs available at /home/vagrant/terraform-ec2-aws/terraform-debug.log.

Here is the screenshot taken from the terraform-debug.log file which shows the DEBUG logs along with the INFO logs:

DEBUG logs and the INFO logs

Here are some sample logs from the terraform-debug.log:

2025-05-19T10:22:14.312Z [INFO]  Terraform version: 1.14.9
2025-05-19T10:22:14.312Z [INFO]  Go runtime version: go1.23.4
2025-05-19T10:22:14.312Z [INFO]  CLI args: []string{"terraform", "init"}
2025-05-19T10:22:14.312Z [DEBUG] Attempting to open CLI config file: /home/user/.terraformrc
2025-05-19T10:22:14.312Z [DEBUG] File doesn't exist, but doesn't need to. Ignoring.
2025-05-19T10:22:14.312Z [DEBUG] ignoring non-existing provider search directory terraform.d/plugins
2025-05-19T10:22:14.312Z [DEBUG] ignoring non-existing provider search directory /home/user/.terraform.d/plugins
2025-05-19T10:22:14.312Z [DEBUG] ignoring non-existing provider search directory /home/user/.local/share/terraform/plugins
2025-05-19T10:22:14.312Z [DEBUG] ignoring non-existing provider search directory /usr/local/share/terraform/plugins

4. Using the error_message to validate variable value

Now that we have seen in the previous section on how to set up the TF_LOG and TF_LOG_PATH, let’s take a look at an example of how to put some custom validation on the Terraform variable, and to see if the validation fails. For this, we are going to log an error_message.

Validation condition: Provision of the EC2 instance should be restricted to t2.nano, t2.micro, t2.small. If the developer wants to provision the t2.medium or any other higher instance, we should restrict the developer with the proper error messages.

Here is a Terraform script with a validation and error_message:

terraform {
  required_version = ">= 1.14.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "ec2_example" {
  ami           = "ami-0c02fb55956c7d316" # Amazon Linux 2 (us-east-1) — update for your region
  instance_type = var.instance_type

  tags = {
    Name = "Terraform EC2"
  }
}

variable "instance_type" {
  description = "Instance type t2.micro"
  type        = string
  default     = "t2.medium"

  validation {
    condition     = can(regex("^[Tt][2-3].(nano|micro|small)", var.instance_type))
    error_message = "Invalid Instance Type name. You can only choose - t2.nano, t2.micro, t2.small"
  }
}

If you look carefully into the variable resource:

  1. condition:  We have defined the regex to allow only – t2.nano, t2.micro, t2.small
  2. error_message: We have set up an error message stating “Invalid Instance Type name. You can only choose – t2.nano,t2.micro,t2.small

So, when the user tries to supply t2.medium then they should get the following error message:

error message

Read more: Terraform Variable Validation in Terraform 1.9 & Earlier Versions

5. Inspect Terraform state file

Inspecting the Terraform state is an essential technique for troubleshooting Terraform configurations and understanding how your infrastructure is currently set up.

The Terraform state file records all the settings and attributes of the defined resources, serving as a source of truth for Terraform to determine resource configurations and detect drift from the desired state.

Use commands like terraform show to examine the current state and terraform plan to find differences between the configurations and the state during debugging. You can find problems like misconfigurations, inconsistencies, or state lock issues by looking in the state file itself – this will help you identify the core cause of any unusual behavior or failures in your infrastructure provisioning.

If you are using remote state, and you want to inspect the state file, you can also use the terraform state pull command to pull the remote state locally and inspect it there.

6. Using terraform console for Interactive Debugging

While TF_LOG is great for capturing what Terraform does during a run, sometimes you need to test and inspect things before running terraform plan or terraform apply. That’s exactly what terraform console is for.

terraform console opens an interactive REPL (Read-Eval-Print Loop) that lets you evaluate Terraform expressions, inspect variable values, and test functions against your real state — all without making any changes to your infrastructure.

Run it in any initialized Terraform directory:

terraform console

A few things you can do instantly from the prompt:

  • Test functions and expressions:
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"
  • Check variable values
> var.instance_type
"t2.micro"
  • Inspect resource attributes from state:
> aws_instance.ec2_example.public_ip
"54.123.45.67"

Type exit or press Ctrl+C to quit.

Alternative: Use external logging tools

By using different levels of debugging, Terraform generates a lot of data that can be hard to follow. Using external logging tools can greatly help with parsing, analyzing, and visualizing this information in a more digestible format. By forwarding Terraform logs to systems such as the ELK Stack (Elasticsearch, Logstash, Kibana), Splunk, or Grafana, you can take advantage of advanced search capabilities, real-time monitoring, and trend analysis.

These tools provide powerful filtering and query options, allowing you to quickly isolate issues, understand resource dependencies, and assess the impact of changes in your infrastructure.

Key points

This post is all about becoming aware of the debugging and logging features provided by Terraform for troubleshooting issues you might face in your development, testing, staging, or production instances.

Setting up the correct info, debug, and error message can significantly lessen your troubleshooting time as well as make your monitoring task. You can even dump the Terraform logs into tools like Splunk or ELK to generate visual graphs.

We encourage you to explore how Spacelift makes it easy to work with Terraform. If you need help managing your Terraform infrastructure, building more complex workflows, or managing AWS credentials per run with dynamic credentials instead of static pairs on a local machine, Spacelift is built for this. 

It supports Git workflows, policy as code, programmatic configuration, context sharing, drift detection, resource visibility, and many more features right out of the box. Spacelift also supports OpenTofu, CloudFormation, Pulumi, Ansible, and Kubernetes, so you can orchestrate your full infrastructure lifecycle from a single platform. You can check it for free by creating a trial account.

Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Discover a better way to manage Terraform

Spacelift helps manage Terraform state, build more complex workflows, and supports policy as code, programmatic configuration, context sharing, drift detection, resource visibility, and many more.

Start free trial

Frequently asked questions

  • How do I enable debug logging in Terraform?

    Set the TF_LOG environment variable to a level like DEBUG or TRACE, and optionally direct output to a file with TF_LOG_PATH=./terraform.log to capture detailed execution information.

  • What is the difference between TF_LOG TRACE and DEBUG?

    TRACE is the most verbose level and includes every internal operation, RPC call, and provider interaction, while DEBUG shows high-level diagnostic information without the deep protocol-level noise.

  • How do I fix a Terraform state lock error?

    Identify the lock holder through the error message, confirm no active operation is running, then run terraform force-unlock <LOCK_ID> to release the lock manually.

  • Where is the Terraform crash log?

    Terraform writes crash logs to crash.log in the working directory whenever the CLI or a provider panics, capturing stack traces and runtime details for debugging.

Terraform Project Structure
Cheat Sheet

Get the Terraform file & project structure

PDF cheat sheet.

terraform files cheat sheet bottom overlay
Share your data and download the cheat sheet