Terraform Drift: Detect and Resolve Infrastructure Drift
Detect Terraform drift across code, state, and provider APIs, expose unmanaged resources, and choose a safe reconciliation path without blind applies.


The problem is not Terraform drift. It is the ungoverned path that let production change without a durable decision reaching code.
Terraform drift exists when the actual attributes of a managed resource no longer match the values Terraform expects from configuration and prior state. A person may edit a security group in a console. A service may rotate, resize, or replace an object. An emergency change may be valid and never make it back to the repository.
Detection is the easy part. The hard part is deciding whether code should restore reality, reality should be written back into code, or risk must be contained before either action. A blind apply turns an evidence problem into an outage problem.
Terraform drift has three competing truths
Code, state, and the provider API can disagree. Resolution starts by deciding which truth should win.
What is Terraform drift?
Terraform operates across three records. Configuration expresses desired intent. State maps each resource address to a real object and stores known attributes. The provider API reports what exists now. A normal plan refreshes remote objects, compares configuration with prior state, and proposes actions that would make actual infrastructure match configuration.
The official Terraform plan command reference makes two warnings operationally important. Using -refresh=false ignores external changes and can produce an incomplete or incorrect plan. Routine use of -target can also create undetected drift and confusion about the relationship among resources.
Not every plan difference is drift. A reviewed code change creates a planned difference. A provider default that becomes explicit can create noise. A computed value that changes on every read may be a provider defect. Drift means the remote object changed outside the accepted configuration path.
What kinds of Terraform drift should teams separate?
One queue should not mix every mismatch. Classify the cause before choosing a fix.
- Approved emergency change. An operator opened access, increased capacity, or changed routing to restore service. The change may be correct, but the repository and approval record are incomplete.
- Unauthorized manual change. A user or automation changed a managed attribute without an approved exception. Code will usually need to restore the value after impact review.
- Service mutation. A cloud service rotated an identifier, added a generated value, or changed an object through its own control plane. The configuration may need a lifecycle or provider adjustment.
- State mapping failure. A resource was moved, imported twice, removed from state, or replaced outside Terraform. The object may be safe while the mapping is wrong.
- Unmanaged infrastructure. A real resource exists but has no state binding or configuration. A normal plan for the workspace cannot report drift for an object it does not know.
The last category is the dangerous blind spot. Teams often report “no drift” when all they proved was “no difference among known objects.” Inventory coverage belongs beside drift status. If 92 percent of cloud resources are managed, a clean plan says nothing about the remaining 8 percent.
How do you automate Terraform drift detection?
Run plans on a schedule with the same locked providers, variables, credentials, backend, and workspace used by the deployment path. Use a principal that can read every managed attribute but cannot change infrastructure. Separate scheduled drift runs from pull request plans so a code change does not disguise an external change.
Terraform gives -detailed-exitcode three precise results: 0 means the plan succeeded with an empty difference, 1 means an error, and 2 means the plan succeeded with changes present. This shell pattern preserves those meanings and exposes the drift object for review:
A saved plan can contain sensitive values. Keep it in protected temporary storage and never commit the binary or JSON form. The Terraform JSON output format documents resource_drift separately from resource_changes. It also records aformat_version, which has value 1.0 as of Terraform 1.1.0, so parsers should accept compatible minor additions and reject unsupported major versions.
Exit code 2 is an admission rule, not an incident by itself. Parse resource address, provider, changed attribute, prior value, current value, workspace, last apply, and actor evidence from the cloud audit log. Then group differences caused by the same event. One console edit that changes a shared network object can affect dozens of dependent services.
What recent Terraform change improves the unmanaged resource problem?
Terraform 1.14.0 was released on November 19, 2025 with two headline features: List Resources and provider defined Actions. The official Terraform 1.14 release notes explain that list resources live in *.tfquery.hcl files and the new terraform querycommand can query existing infrastructure, filter results, and optionally generate configuration for import.
That matters because old drift guidance usually begins and ends with terraform plan. Plan can compare objects already bound in state. Query support gives providers a path to list existing objects that may sit outside state, subject to provider implementation and access. It does not make every resource managed. It helps expose inventory that needs an ownership decision.
Build two checks. The first compares known objects with remote state. The second compares provider inventory with managed inventory. Call the first configuration drift and the second management coverage. Mixing them produces a reassuring number that answers neither question.
How should you resolve Terraform drift?
Choose one of three lanes and record the decision before execution.
- Code wins. The external change was unauthorized, expired, or unsafe. Review the plan, assess dependent resources, apply through the normal pipeline, and retest service and security state.
- Reality wins. The external change is the approved target. Update configuration, review the code change, import or move state if needed, then plan until no unintended difference remains.
- Contain first. The drift creates immediate exposure or the safe target is unclear. Restrict access, reduce privilege, or isolate the resource. Reconcile code and state after stability returns.
Do not use terraform apply -refresh-only as a universal cleanup. Refresh only mode updates state and outputs to match remote objects. It does not change remote infrastructure to match configuration. That can be correct when reality is approved, but it can also normalize an unauthorized change and erase the visible difference without repairing the control.
State repair deserves the same caution. One real object should map to one resource instance. Importing it into a second address or removing the wrong binding can make a clean plan hide conflicting ownership. Save current evidence, verify the resource identity, review every state command, and run a fresh plan afterward.
What does a good Terraform drift decision look like?
Imagine a production security group whose code allows SSH only from a managed administration range. An engineer opens port 22 to the internet during a failed recovery, service returns, and the emergency record expires without a repository change. The next scheduled plan reports that the remote ingress range differs from configuration.
The drift event should include the security group address, old and current ranges, cloud audit actor, change time, incident record, resource owner, attached services, public route, and any active session evidence. The immediate decision is not “apply Terraform.” It is whether the broad route is still needed. If not, remove it through the reviewed pipeline and confirm recovery access still works. If it is needed, replace it with a narrower approved route and update code before the exception expires.
Now change one fact. The engineer created a second security group and attached it to the instance, but the new group was never imported. The workspace plan may report a changed attachment while an inventory comparison reveals the unmanaged group itself. Both records belong in one case. Restoring only the managed attachment can leave the broad group available for reuse elsewhere.
Closure requires a clean plan, a provider inventory check, the expected network test, and an audit record for the repair. A clean plan alone proves consistency for known objects. It does not prove that the dangerous path disappeared.
How do you prevent Terraform drift without blocking emergency work?
Give operators a controlled emergency lane. Require a reason, incident or change record, approved scope, named owner, expiry, and follow up code change. Alert on the event immediately, but do not force a responder to choose between restoring service and obeying a slow repository process.
Protect state too. Terraform locks state automatically for write operations when the backend supports it. The Terraform state locking documentation warns that disabling locks is not recommended and that force unlock should target only your own failed lock using the unique lock ID. Two writers can corrupt state without creating infrastructure drift first.
Reduce standing console privilege, log every provider API change, and make the repository path fast enough for ordinary work. Most manual drift is an incentive signal. If a two minute change requires a two day review, operators will find the console. Fix the path, then enforce it.
Use fleet drift detection to connect cloud differences with host and service state. The drift remediation guide covers staged repair and rollback, while cloud misconfiguration analysis shows how exposure, identity, privilege, and data turn one attribute change into actual risk.
What should a Terraform drift program measure?
Count workspaces checked, resources observed, managed inventory coverage, drifted resources, oldest drift, unauthorized changes, approved emergency changes, time to owner, and time to reconciliation. Report errors separately. A plan that failed to authenticate is unknown, not clean.
Capacity math keeps the schedule honest. Eighty workspaces at four minutes per plan consume 320 runner minutes, or five hours and 20 minutes, per daily pass. If ten workspaces produce differences and three engineers each spend 15 minutes reconstructing context, triage consumes seven and a half labor hours. Enrich the event once with audit actor, change record, resource owner, and risk context.
Run critical identity, network, and public data workspaces more often than low consequence development workspaces. Frequency should follow the speed and impact of unauthorized change, not one global cron schedule. Artemes uses deep endpoint context with AI driven analysis to help connect observed configuration to exposure and exact remediation. The same principle applies in cloud control planes: context must reach the drift event before a person is asked to decide.
Frequently asked questions
Does Terraform plan detect all drift?
No. It detects differences for resources Terraform already knows through configuration and state. It cannot prove that every real resource is managed, and provider behavior can hide or normalize some attributes.
Should Terraform drift be remediated automatically?
Auto apply is reasonable only for narrow, tested, reversible values with clear ownership and low operational impact. Identity, network, data, deletion, and replacement changes need stronger gates.
What is the difference between refresh only and apply?
Refresh only updates Terraform state to match remote objects. A normal apply changes remote objects toward configuration. Choose based on which state was approved, not which command makes the alert disappear.
How often should Terraform drift checks run?
Match frequency to consequence and expected change speed. Critical production workspaces may need hourly checks. Low consequence development workspaces may be adequate daily. Track detection delay and adjust.
The executive takeaway
Schedule a read only plan for every production workspace, preserve exit codes, parse drift separately from planned changes, and compare provider inventory with managed inventory. Give each result one of three decisions: code wins, reality wins, or contain first. Close it only when configuration, state, remote infrastructure, and ownership agree.
Put more evidence behind vulnerability decisions
Artemes AI combines endpoint telemetry, sourced vulnerability intelligence, and review-gated analysis so teams can examine the evidence, missing context, and recommended next step together. We are accepting early-access requests now.

Alex Gibson
Alex writes about configuration drift, operational security evidence, endpoint telemetry, AI-assisted triage, and the practical work of turning signals into better remediation decisions.
Related Reading
Get articles like this in your inbox.
Security research and occasional Artemes AI product updates.



