Terraform helps teams manage cloud infrastructure through configuration files instead of relying on manual changes in cloud consoles. You can define virtual machines, networks, databases, permissions, and other resources in code, then use Terraform to create and update them.
However, infrastructure does not always remain consistent with that code. An administrator might change a security group in the cloud console, a deployment script might update a resource directly, or another team might modify a database setting without updating Terraform.
These differences are known as configuration drift.
Drift can create security gaps, unexpected costs, and deployment failures. It can also make the next Terraform deployment riskier because Terraform may attempt to change resources that no longer match the intended configuration.
This article explains how Terraform drift detection works, how to identify changes using terraform plan, how refresh-only operations help reconcile state, and how to make drift checks part of a regular infrastructure workflow.
What Is Terraform Drift?
Terraform manages infrastructure using configuration files, a state file, and the actual resources deployed in your cloud environment.
These three components serve different purposes:
- Configuration: Describes the infrastructure you want.
- Terraform state: Records Terraform's understanding of the managed resources and their attributes.
- Remote infrastructure: Represents the resources and settings that currently exist in the cloud provider.
Drift occurs when the real infrastructure changes outside the expected Terraform workflow or otherwise becomes different from the configuration Terraform is intended to manage.
Consider a security group that allows HTTPS traffic on port 443. An administrator manually adds a rule allowing inbound traffic on port 8080.
The cloud resource now contains a rule that is absent from the Terraform configuration. Terraform can detect this difference during planning, provided the resource is managed by Terraform and the provider can observe the relevant attribute.
Why Infrastructure Drift Matters
Drift is not always immediately visible to application users, but it can create operational problems.
For example:
- A firewall rule may expose a service that should remain private.
- A database setting may differ from the approved configuration.
- A virtual machine may have been resized manually, changing the expected infrastructure cost.
- A manually removed resource may cause a dependent application to fail.
- A future Terraform deployment may overwrite an intentional emergency change.
The main concern is not simply that configuration has changed. It is that the infrastructure may no longer match the documented and reviewed design.
How Terraform Detects Drift
Terraform compares the configuration with its current understanding of the managed infrastructure. During a plan operation, it can refresh resource information from the provider and identify differences that require changes.
A simplified workflow looks like this:
Terraform Configuration
|
v
Refresh Managed Resource State
|
v
Compare Configuration and Observed Resources
|
v
Generate Execution Plan
|
v
Review Proposed Changes
|
v
Apply Only After Approval
The execution plan describes what Terraform proposes to change. It may include updates, resource replacements, creations, or deletions.
A plan does not automatically correct drift. It shows the actions Terraform believes are necessary to move the infrastructure toward the configuration.
The exact result depends on the provider, the resource schema, the available permissions, and how the configuration represents the resource.
Step 1: Review the Terraform Configuration
Start with a simple example that manages an AWS security group.
The following configuration illustrates a security group that permits inbound HTTPS traffic:
resource "aws_security_group" "web" {
name = "web-security-group"
description = "Allow HTTPS traffic"
vpc_id = var.vpc_id
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
This example assumes that var.vpc_id identifies an existing VPC and that the selected AWS provider version supports the configuration.
Now imagine that someone adds another inbound rule through the AWS console, allowing traffic on port 8080.
The real security group no longer matches the intended configuration. Because the additional rule is not represented in the Terraform code, a subsequent plan may propose removing it.
That behavior is useful when the manual change is unauthorized. If the change was intentional, review it before applying anything.
Step 2: Run Terraform Plan
The simplest way to investigate possible drift is to run a plan from the correct Terraform working directory.
First, initialize the working directory if necessary:
terraform init
Validate the configuration:
terraform validate
Then run:
terraform plan
During planning, Terraform normally refreshes information about managed resources before calculating proposed changes.
Review the plan output carefully. You may see a change resembling this:
# aws_security_group.web will be updated in-place
~ resource "aws_security_group" "web" {
~ ingress = [
# Existing HTTPS rule
...
- {
from_port = 8080
to_port = 8080
protocol = "tcp"
...
}
]
}
This is an illustrative example. Actual output depends on the AWS provider version and how the resource represents security group rules.
The important detail is the proposed removal of a rule that is not present in the configuration.
Understand the Plan Symbols
Terraform uses symbols to communicate proposed changes.
| Symbol | Meaning |
|---|
+ | Create a resource or add an attribute |
- | Destroy a resource or remove an attribute |
~ | Update a resource in place |
-/+ | Destroy and recreate a resource |
<= | Read a data source during planning |
A resource replacement deserves particular attention because it can affect availability or destroy and recreate infrastructure.
Do not approve a plan simply because it contains only a few changes. Review resource addresses, sensitive attributes, replacement behavior, and the consequences of every significant action.
Step 3: Distinguish Drift from Configuration Changes
Not every Terraform plan difference is caused by manual infrastructure changes.
A plan can show changes because:
- The Terraform configuration has changed.
- Someone modified a resource outside Terraform.
- A provider reports a normalized or computed value differently.
- A remote system changes an attribute automatically.
- A provider version changes how an attribute is represented.
- A data source returns a different result.
Terraform plans may contain a mixture of configuration changes and drift-related differences.
To investigate, compare the proposed changes with recent code commits, cloud audit logs, and the resource's current settings.
For example, if a pull request intentionally changes an instance size, an update to that size is expected. If the instance size changed in the cloud console without a corresponding configuration change, it may indicate drift.
The plan shows what Terraform intends to do, but it does not always explain why a remote attribute changed. Cloud audit logs and deployment records can help establish the cause.
Step 4: Use Refresh-Only Mode Carefully
Terraform provides a refresh-only planning mode for reviewing differences between the current state and remote infrastructure.
Run:
terraform plan -refresh-only
This mode focuses on changes to Terraform state that reflect the observed remote infrastructure. It does not propose changing the remote resources to match the configuration in the same way as a normal plan.
If the differences are understood and should be recorded in state, you can apply the refresh-only plan:
terraform apply -refresh-only
Review the proposed state changes before confirming the operation.
When Should You Use Refresh-Only Mode?
Refresh-only mode is useful when you need Terraform's state to reflect a legitimate change that has already occurred in the remote environment.
For example, an administrator may have changed an attribute that the team intends to keep. Refresh-only mode can update Terraform's recorded understanding of the resource without asking Terraform to modify the remote resource as part of that operation.
However, refreshing state does not update your Terraform configuration files.
If the configuration still specifies the old value, a normal plan may propose changing the remote resource back to that value.
A safer workflow for an approved manual change is:
- Investigate the change and confirm it is intentional.
- Update the Terraform configuration to reflect the approved design.
- Review the resulting plan.
- Apply the required changes through the normal workflow.
Use refresh-only mode when reconciling state is appropriate, not as a substitute for configuration management.
Step 5: Save and Review a Terraform Plan
For production environments, saving a plan makes it easier to review the exact set of actions before applying them.
Run:
terraform plan -out=tfplan
Inspect the saved plan:
terraform show tfplan
After review and approval, you can apply the saved plan:
terraform apply tfplan
A saved plan provides a record of the proposed actions, but it should still be handled securely. Plan files can contain sensitive values, so avoid committing them to source control or publishing them in unrestricted build artifacts.
Also, saved plans are tied to the configuration and state context used to create them. If the infrastructure or execution context changes, regenerate and review the plan rather than assuming an older plan remains appropriate.
For a routine drift investigation, begin with a normal plan. Save a plan when you need a controlled review and approval process.
Step 6: Automate Drift Detection
Running Terraform manually is useful during development, but production infrastructure benefits from scheduled checks.
A CI/CD system can execute a plan on a schedule or after infrastructure-related changes. The workflow can then report unexpected differences to the responsible team.
A basic shell script might look like this:
#!/usr/bin/env bash
set -euo pipefail
terraform init -input=false
terraform validate
terraform plan \
-input=false \
-detailed-exitcode
The -detailed-exitcode option changes Terraform's exit behavior:
| Exit code | Meaning |
|---|
0 | The plan completed successfully and found no changes |
1 | An error occurred |
2 | The plan completed successfully and found changes |
A scheduled job must interpret these exit codes correctly. If the pipeline treats exit code 2 as an ordinary failure, it may report every detected change as a failed job rather than a drift alert.
Also, a nonzero plan result does not prove that manual drift occurred. It may represent an intentional configuration change or another difference. Your workflow should distinguish expected changes from unexpected ones.
Use a Dedicated Drift Monitoring Workflow
A practical monitoring workflow should:
- Run against the correct workspace and backend.
- Use the intended variables and provider credentials.
- Generate a fresh plan.
- Identify whether changes were expected.
- Notify the team about unexplained differences.
- Require review before any corrective apply operation.
For larger environments, use a managed Terraform workflow or an established CI system that supports scheduled plans, access controls, and notifications.
Avoid automatically applying every drift correction without review. An unexpected difference may represent an emergency fix, a cloud service's computed behavior, or a legitimate change that has not yet been documented.
Common Terraform Drift Detection Problems
Terraform Reports Changes on Every Plan
Some differences result from provider normalization, computed attributes, or remote systems that modify values automatically.
How to investigate: Check the resource documentation, provider version, and plan details. Confirm whether the attribute is configurable, computed, or expected to change remotely.
Use ignore_changes only when there is a deliberate reason to let another system manage specific attributes. Overusing it can hide meaningful drift.
A Manual Change Does Not Appear in the Plan
Terraform cannot reliably detect every change in every remote system. Detection depends on the provider's ability to read the attribute and compare it with the configuration and state.
How to investigate: Confirm that the resource is managed by Terraform, the relevant attribute is exposed by the provider, and the execution identity has the necessary read permissions.
Refresh-Only Mode Does Not Fix the Configuration
Refresh-only mode updates Terraform's understanding of remote resources; it does not rewrite the configuration files.
How to investigate: Update the configuration if the remote change is approved, or use a normal plan to review the changes required to restore the intended configuration.
A Plan Proposes Resource Replacement
Some changes require Terraform to destroy and recreate a resource rather than update it in place.
How to investigate: Review the provider's replacement requirements and assess availability, data retention, dependencies, and recovery options before applying the plan.
Drift Checks Fail in CI
Automated checks can fail because credentials expired, the wrong workspace was selected, or the state backend is unavailable.
How to investigate: Verify authentication, backend configuration, workspace selection, and provider versions. Ensure that the CI identity has read access to the resources being checked.
Best Practices for Production Infrastructure
Use these practices to make drift detection part of a reliable infrastructure management process.
- Use version control: Keep Terraform configuration in a repository so approved changes can be reviewed and traced.
- Restrict manual modifications: Limit direct cloud-console changes where possible, while maintaining a controlled emergency-access process.
- Run regular plans: Schedule drift checks for important environments and review unexpected changes promptly.
- Separate detection from remediation: Report unexpected changes first, then decide whether to update the configuration or restore the intended infrastructure.
- Review destructive actions: Pay particular attention to deletions, replacements, firewall changes, and database modifications.
- Keep provider versions controlled: Use an appropriate version constraint and commit the dependency lock file so that provider upgrades are deliberate.
- Protect state and plan files: Treat them as sensitive infrastructure artifacts.
- Use least-privilege credentials: Give monitoring jobs the permissions they need without granting unnecessary modification access.
- Document emergency changes: Require teams to reconcile approved manual changes with Terraform configuration afterward.
- Monitor important cloud resources: Use cloud audit logs and monitoring tools to help explain who changed a resource and when.
Advantages and Limitations of Terraform Drift Detection
| Advantages | Limitations |
|---|
| Identifies differences between configuration and managed infrastructure | Detection depends on provider capabilities and resource visibility |
| Helps uncover unauthorized configuration changes | Does not automatically explain who made a change |
| Reduces surprises during future deployments | Plans can contain both intentional changes and drift |
| Supports repeatable infrastructure reviews | Scheduled checks require correct credentials and workspace configuration |
| Improves infrastructure security and consistency | Blindly applying corrections can undo legitimate emergency changes |
Terraform drift detection is most effective when paired with access controls, change reviews, cloud audit logs, and a clear process for reconciling approved changes.
Summary
Terraform drift occurs when managed infrastructure no longer matches the configuration and state expected by your infrastructure workflow. Manual console changes, external automation, and remote system behavior can all contribute to differences that need investigation.
Start with terraform plan to identify proposed changes. Use terraform plan -refresh-only when you need to review how remote infrastructure differs from the recorded state, and apply a refresh-only plan only after understanding its effects.
For production environments, automate scheduled plans, interpret their exit codes correctly, and alert teams about unexpected differences. Most importantly, do not automatically apply every proposed correction. Determine whether the change should be accepted into the configuration or reversed to restore the intended infrastructure.
A consistent drift detection process helps teams keep cloud infrastructure secure, predictable, and aligned with the code used to manage it.
Join the conversation! Your thoughts help the community grow.