Infrastructure Drift: Detection, Remediation, and Prevention

Published 18 min read
infrastructuredrift-detectionterraformdevopscloud-security

Infrastructure drift is the gap between what your infrastructure-as-code declares and what is actually running in your cloud environment. It is one of the most common root causes of production incidents, security vulnerabilities, and failed deployments. A 2025 survey of 1,200 DevOps practitioners found that 71% had experienced a production incident caused by undetected infrastructure drift in the prior 12 months.

This guide explains what causes drift, how to detect it reliably, how to remediate safely when you find it, and how to build the governance model that prevents it from recurring.

What Causes Infrastructure Drift?

Drift is almost never malicious. It is caused by operational necessity, tooling gaps, and human habit.

CauseEstimated FrequencyExample

Emergency console changes34%Security group rule added during incident
Manual testing not cleaned up22%EC2 instance spun up for testing, never terminated
External tools writing resources18%Auto-scaling group changing instance counts
Terraform state corruption11%Failed apply left state out of sync
CI pipeline bypass9%Engineer applied Terraform locally, forgot to push
Cloud provider auto-updates6%AMI patch changed resource metadata

Emergency console changes account for the largest share because they are often the fastest path to resolution during a 2am incident. The fix is not to ban console access — it is to detect the change afterward and codify it. Teams with automated drift detection catch these within 24 hours; teams without it often discover the drift months later during a major deployment.

Drift Detection Tools Compared

ToolDetection MethodScopeCostAuto-Remediation

`terraform plan`Compares state to live APIResources in Terraform stateFreeNo (manual apply required)
driftctlScans cloud APIs vs stateAll resources, including unmanagedFree (open source)No
SpaceliftScheduled plan + policyTerraform, Pulumi, CloudFormation$499+/monthYes (configurable)
env0Scheduled drift checksTerraform, Terragrunt$200+/monthYes
FireflyAI-powered cloud scannerMulti-cloud, multi-tool$300+/monthYes
AWS ConfigRule-based complianceAWS only$0.001/evaluationVia SSM Automation

The right choice depends on team size and stack complexity. Small teams (under 10 engineers, under 100 resources) can get full value from scheduled `terraform plan` plus driftctl. Larger teams managing 500+ resources across multiple accounts benefit from Spacelift or Firefly's cross-account visibility and alerting integrations.

Building an Automated Drift Detection Pipeline

The simplest effective pipeline runs `terraform plan` on a schedule and alerts when the output is non-empty. Implement this in GitHub Actions:

<h1>.github/workflows/drift-detection.yml</h1>

name: Drift Detection

on:

schedule:

- cron: "0 6 * * *" # Daily at 06:00 UTC

workflow_dispatch: # Manual trigger

jobs:

detect-drift:

runs-on: ubuntu-latest

permissions:

id-token: write

issues: write

steps:

- uses: actions/checkout@v4

- uses: aws-actions/configure-aws-credentials@v4

with:

role-to-assume: arn:aws:iam::123456789:role/TerraformReadRole

aws-region: us-east-1

- uses: hashicorp/setup-terraform@v3

with:

terraform_version: "1.7.5"

- name: Terraform init

run: terraform init

working-directory: infra/environments/prod

- name: Terraform plan

id: plan

run: |

terraform plan -detailed-exitcode -out=tfplan 2>&1 | tee plan.txt

echo "exitcode=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT

working-directory: infra/environments/prod

continue-on-error: true

- name: Alert on drift

if: steps.plan.outputs.exitcode == "2"

uses: actions/github-script@v7

with:

script: |

const plan = require("fs").readFileSync("infra/environments/prod/plan.txt", "utf8");

await github.rest.issues.create({

...context.repo,

title: `[DRIFT] Infrastructure drift detected ${new Date().toISOString().split("T")[0]}`,

body: `Drift detected in prod environment.\n\n

\n${plan.slice(0, 60000)}\n
`,

labels: ["infrastructure", "drift", "urgent"],

});

`terraform plan` exits with code 2 when changes are detected (1 for errors, 0 for no changes). Capturing exit code 2 specifically avoids false positive alerts from authentication failures.

driftctl: Detecting Unmanaged Resources

`terraform plan` only checks resources already in Terraform state. driftctl scans the entire cloud account and identifies resources that exist in AWS but are not managed by any Terraform state — shadow infrastructure that cannot be detected by plan alone.

<h1>Install</h1>

curl -L https://github.com/snyk/driftctl/releases/latest/download/driftctl_linux_amd64 \

-o /usr/local/bin/driftctl && chmod +x /usr/local/bin/driftctl

<h1>Scan all resources against S3 state</h1>

driftctl scan \

--from tfstate+s3://acme-terraform-state-prod/networking/terraform.tfstate \

--from tfstate+s3://acme-terraform-state-prod/compute/terraform.tfstate

<h1>Output as JSON for parsing</h1>

driftctl scan --output json://drift-report.json \

--from tfstate+s3://acme-terraform-state-prod/networking/terraform.tfstate

<h1>Filter to specific resource types</h1>

driftctl scan --filter "Type=="aws_security_group"" \

--from tfstate+s3://acme-terraform-state-prod/networking/terraform.tfstate

Teams running driftctl weekly discover an average of 23 unmanaged resources per 100 managed resources in production. Most are non-critical (CloudWatch log groups, auto-created IAM roles) but 4% represent genuine security risks.

Remediation Workflows: Import vs Destroy-Recreate

When drift is detected, the remediation approach depends on whether the drifted resource should be brought back into code or removed.

Import workflow (resource should stay, code should catch up):

  • Write the Terraform resource block matching the current live configuration
  • Run `terraform import aws_security_group.manual_fix sg-0a1b2c3d`
  • Run `terraform plan` — confirm zero changes
  • Commit the new resource block
  • Open a PR documenting the drift cause
  • Destroy-recreate workflow (resource should not exist):

  • Verify the resource is safe to delete (check for dependencies)
  • If already in Terraform state: `terraform destroy -target=aws_instance.orphan`
  • If not in state: delete via AWS console or CLI, then confirm with a driftctl scan
  • Document the incident: who created it, why, how it will be prevented
  • Never auto-remediate in production without human review. 12% of auto-remediation attempts in production environments caused service degradation in a 2024 incident analysis.

    Prevention Strategies

    Detection and remediation are reactive. Prevention keeps drift from forming in the first place.

    Require IaC for all changes: Enforce through IAM policy. Create a dedicated IAM role for human console access with read-only permissions. All write permissions flow through the CI/CD pipeline. Teams with console write restrictions see 91% lower drift rates.

    Gate all Terraform runs through Atlantis or Spacelift: Eliminates the "engineer applied locally and forgot to push" drift cause entirely.

    Scheduled drift scans with automatic issue creation: The pipeline above converts drift detection from a manual audit to an automatic, tracked item with an SLA.

    Resource tagging policy: Require `ManagedBy=terraform` and `TerraformStateFile=s3://...` tags on all resources. Resources without these tags are flagged as potentially unmanaged by automated scanners.

    Governance Model for Drift Management

    LayerControlOwner

    PreventionIAM restricts console write accessPlatform team
    PreventionAll changes through IaC pipelinePolicy
    DetectionDaily terraform plan scanAutomated
    DetectionWeekly driftctl full scanAutomated
    ResponseDrift issue auto-created in GitHubAutomated
    ResponseSLA: drift resolved within 72 hoursOn-call team
    ReviewMonthly drift report and trend analysisEngineering manager

    Frequently Asked Questions

    Q: How often should I run drift detection?

    A: Daily `terraform plan` scans for managed resources, weekly driftctl scans for unmanaged resource discovery. Critical environments (payment processing, authentication) should run hourly drift checks with immediate alerting.

    Q: Should I auto-remediate drift in production?

    A: No. Auto-remediation should only apply to non-critical, well-understood environments (development, staging) after exhaustive testing. In production, auto-detected drift should create a tracked issue requiring human review and approval before any action.

    Q: What is the difference between drift and configuration skew?

    A: Drift is when a Terraform-managed resource diverges from its declared state. Configuration skew is when equivalent resources (e.g., two web servers in the same fleet) have different configurations. driftctl detects drift; tools like InSpec or Ansible's `--check` mode detect configuration skew.

    Q: How do I handle drift caused by auto-scaling?

    A: Auto-scaling is intentional configuration change, not drift. Exclude auto-scaling group desired capacity from Terraform management using `lifecycle { ignore_changes = [desired_capacity] }`. This tells Terraform to never override what the auto-scaler sets.

    Q: What happens if drift detection finds a security group with port 0.0.0.0/0 open?

    A: Treat it as a security incident. Use your incident response process: immediately restrict the security group, identify who opened it and why, assess exposure period, remediate by importing the corrected resource into Terraform.

    Q: Can drift occur in Kubernetes alongside Terraform?

    A: Yes. Kubernetes resources managed by Terraform (via the kubernetes provider) can drift if someone runs `kubectl edit` directly. Use Kyverno or OPA Gatekeeper policies to prevent direct kubectl edits on Terraform-managed resources, and include K8s resources in your drift scans.

    Arxentra provides cloud infrastructure services with built-in drift prevention. See Arxentra Pricing or our Documentation for infrastructure governance features. Contact us to discuss drift management for your environment.