Terraform Refresh
Terraform refresh reconciles the local state file with the actual state of infrastructure by querying the provider APIs. Terraform 1.x changed how refresh works — the standalone terraform refresh command is now deprecated in favor of -refresh-only mode built into plan and apply.
The Old Way: terraform refresh (Deprecated)
# Terraform 0.x — standalone refresh commandterraform refresh # Updates state to match real infra, no plan shown
# Problems with this approach:# 1. Silently modified the state file# 2. No way to preview what changes would be made to state# 3. Could accept drift you didn't want to keepThe New Way: -refresh-only (Terraform 1.x+)
-refresh-only makes refresh safe by separating detection from acceptance:
# Step 1: Preview what drift exists (read-only, no changes made)terraform plan -refresh-only
# Step 2: If you want to accept the drift, update the stateterraform apply -refresh-onlyThe -refresh-only plan shows only what changed in real infrastructure, not configuration changes — making it a precise drift detection tool.
How Refresh Works During Normal Plan/Apply
Refresh is enabled by default in every terraform plan and terraform apply:
# Default — runs refresh before planningterraform plan
# Equivalent to:terraform plan -refresh=true
# Skip refresh (use cached state — faster but might miss drift)terraform plan -refresh=falseThe sequence for a normal terraform apply:
- Refresh — query provider APIs to update state with current real-world values
- Plan — compare config (desired) against refreshed state (actual)
- Diff — compute what changes are needed
- Apply — execute the changes
- Update state — write the new state after changes
terraform plan -refresh-only
Use this to understand what changed in real infrastructure without modifying it:
terraform plan -refresh-only
# Output shows only real-world changes:Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraformsince the last "terraform apply":
~ resource "aws_instance" "web" { ~ instance_type = "t3.micro" -> "t3.small" # Changed in console }
~ resource "aws_security_group" "app" { ~ ingress = [ # Manually added SSH rule + { from_port = 22, to_port = 22, protocol = "tcp", cidr_blocks = ["10.0.0.0/8"] } ] }
This is a summary of detected drift, not a plan to apply changes.To update the state to match, run: terraform apply -refresh-onlyterraform apply -refresh-only
Accept detected drift by updating the state file:
terraform apply -refresh-only
# Only changes state — does NOT modify real infrastructure# After this, the state matches what's actually deployed# Your .tf config now differs from state — update it to matchThe typical drift management workflow:
# 1. Detect driftterraform plan -refresh-only
# 2a. Accept the drift (keep the manual change)terraform apply -refresh-only# Then update .tf config to codify the change
# 2b. Reject the drift (restore to Terraform's desired state)terraform plan # Shows what needs to change to restore configterraform apply # Restores infrastructure to match configSkipping Refresh for Speed
Refresh is the slowest part of terraform plan for large configurations — it makes an API call for every resource. Skip it when you’re iterating on config changes and you’re confident no drift occurred:
# Skip refresh — use state as-is, much faster for large configsterraform plan -refresh=false
# Skip refresh for apply (be careful — state might be stale)terraform apply -refresh=falseWhen it’s safe to skip:
- You just ran a plan with refresh and nothing changed
- You’re iterating on a new resource that doesn’t exist yet
- You need a fast feedback loop during development
When NOT to skip:
- Before production deploys
- When auto-scaling or external processes might have modified resources
- When you haven’t run Terraform recently
Refresh with Targets
For large configurations, refresh only specific resources:
# Only refresh the database instance and its associated security groupterraform plan -refresh-only \ -target=aws_db_instance.main \ -target=aws_security_group.databaseCommon Refresh Scenarios
# Scenario 1: EKS node group was manually scaled# Detect and accept the new node countterraform plan -refresh-onlyterraform apply -refresh-only# Update variables.tf to set the new desired count
# Scenario 2: RDS instance was manually upgraded (instance type changed)# Detect the changeterraform plan -refresh-only# Decide: accept (and update .tf) or reject (terraform apply to restore)
# Scenario 3: Route53 record was manually updated# Check if intentional, then either accept or revertterraform plan -refresh-only -target=aws_route53_record.api
# Scenario 4: Security group rule was accidentally deleted# Plan will show Terraform wants to recreate itterraform plan # Note: not refresh-only — this is a normal planterraform apply # Recreate the rule