- Section
- cloud-devops
- Kind
- Editorial
- Reading time
- 5 min
- Updated
- 8 September 2026
Detect and Fix Terraform Drift: Practices That Keep State in Sync
Terraform's plan -refresh-only and apply -refresh-only functionality provide a reliable path to managing drift between codified infrastructure and the real-world state of a system. When overlooked resources, auto-scaled state that whole teams didn't know had grown into, or a legacy resource no longer in active use drift out of sync with a team's Terraform definitions, plan -refresh-only safely shows where those gaps lie, while apply -refresh-only helps bring state documents in line with reality without touching those deployed resources.
How Drift is Detected
Whenever a terraform plan is run, Terraform implicitly refreshes the loaded state file to match the current state from the backend. By default, this refresh considers changes between the plan and the remote backend state file, and it compares that to the current configuration. A terraform plan on its own describes both the differences between configuration and remote state, as well as differences between the remote state and the current reality of the resources.
terraform plan -refresh-only, on the other hand, immediately refreshes state in memory after checking out the configuration, but expresses only the changes that would be made to the remote state file, not any planned infrastructure changes. Its output lets you check state file integrity without showing any infrastructure modifications, and its exit code (terraform plan -detailed-exitcode) returns 2 only when the difference means that resources need an update to resolve drift. This lets the command be used reliably in CI pipelines, and enables scheduled comparison checks to detect drift before it reaches a breaking point.
These perimeter checks look for unmanaged changes to any resources that fall under the given Terraform configuration, but that detected difference is only part of the work: once a discrepancy between plan, state, and reality is found, the changes still need to be imported or manually reconciled.
What Counts as Unmanaged Reality
In the import workflow to bring existing, unmanaged infrastructure under Terraform control, Terraform retrieves the details of an out-of-band resource, then generates an auto-created config for it. Teams are then expected to review the imported configuration, remove any unnecessary or stale configuration, then apply the revised import output.
In one sense, an import without pruning is like letting resources define their own favored configuration. In practice, it means storing the current state of the most specific and over-engineered configuration any resource owner can imagine, complete with all the defaults and explicitly defined backed-off values any particular resource has.
HashiCorp's import tutorial makes clear that a working import needs to be edited by hand to remove stale configuration before being merged. Teams following an import must remember to prune the contextual defaults, delete in-line code comments, and bring the configuration closer to the team's overall copy style while keeping import logic true to the resource in question.
Import is a powerful way to come up with an idea of what current resources might be built from, but it should not carry the inertia of full manageability on its own. After an import, whether nested random-auto-generated names or over-revisioned class references, the resulting configuration needs editorial or remixing work to serve its long-term role in a living system.
Who Holds the State
Terraform backends store the full infrastructure state and manage state locks to ensure that only one terraform plan or terraform apply is executed at a time. Remote state backends like S3, PostgreSQL, and Azure Blob Storage provide state storage, but need permissions policies and workflows to handle state custody. Production environments need stronger access controls, with many production teams restricting read-only access of state to all but a privileged few, and keeping manual state changes and imports to that highly-privileged access group.
In the security space, Microsoft's Zero Trust assessment practices-especially its total machine trust model-recommend separate state backends and restrictive permissions for each logical application to minimize the blast radius of a bad application. Even outside Zero Trust, gating state.yaml access, and limiting permissions or restricting backend access by role-type can check deviation outside of the team's collaboration workflows.
How to Catch Drift Before Production Does
Scheduled terraform plan -detailed-exitcode assessments, run by trusted machine identities, let teams fragment the smallest possible span of drift from its next move into the main codebase. When a pipeline detects a change, it would at least notify a responsible party. Integrations with GitHub Actions, Azure DevOps, and other CI systems let teams make state change detection a daily arc of their CI and guardrails pipeline.
Named-critical checks for drift detect the smallest span of potential issues. In practice, after a merge and before tagging a release, trusted identity runners validate the head of the main branch using plan -refresh-only to announce any changes that need reconciliations before deployment, thus localizing drift not to flag the release as a whole, but to highly curated differencing that can be synchronized before the release pipeline ever encounters drift.
What a Recovery Path Looks Like
Empirical assessments can't be too far out from a disaster recovery path. If a tool can detect an out-of-process change, it should be able to explain a path to make a targeted change. When Terraform refreshes state in the face of unexpected change, there must be a corresponding plan for a targeted import or reconciliation. Both import and terraform state pull provide ways to check remote state, either by exporting state definitions to compare with Terraform definitions, or by changing internal state to match reality. These drills, and the remediation paths that do come from them, provide a check that the drift detection functions we take as established fact actually provide a holistic path to managing drift.
Recovering from a drift scenario is not a science for science's sake. Practicing importing instead of recreating, drills teams to use the terraform plan / terraform apply / terraform import workflow. Bringing existing resources under Terraform control-"importing" them-is almost a universal response to a state-drift scenario. Even outside infrastructure drift, sharpening import workflows through practice drills improves teams' understanding of import, and minimizes downtime when infrastructure drift is discovered.
What Not to Assume
terraform plan -refresh-only is worth pointing out in these docs: Terraform itself detects drift and remotely notices changes in Terraform-managed resources-a report between current state and resource configuration. But it helps guard against an equally dangerous drift scenario: the gap between drift detection and reconciling that drift.
Imperative changes, like editing a server's configuration file, resizing a disk, or setting a new network policy, are inevitably going to diverge from the group's Terraform-defined state. But they shouldn't be managed by guesswork. Teams need practice, and practice mapped to production, to make a Terraform detection report into a hands-on guidance to an improved production system.