
September 10, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Production teams change servers, load balancers, and databases every week. A bad Terraform apply can drop traffic mid-request. Zero-Downtime Infrastructure Updates with Terraform mean you replace or resize resources while healthy instances keep serving users. This guide covers the patterns I use alongside Terraform infrastructure-as-code workflows and application deploys on real client stacks. You will get copy-paste HCL, CI guardrails, and the failure modes that still bite experienced teams.
create_before_destroy, rolling replacement via autoscaling groups or load balancers, and health-checked cutover so new resources pass checks before old ones terminate. Always run terraform plan in CI and pin provider versions.What does zero downtime mean when Terraform updates infrastructure?
Zero downtime at the infrastructure layer is not magic. It means user requests succeed while underlying compute, networking, or storage changes. Application deploys handle code swaps. Terraform handles the platform beneath that code.
On a production Laravel application I maintain, Deployer swaps PHP releases with symlinked directories. Terraform provisions the EC2 instances, security groups, and load balancer those releases run on. Both layers must cooperate. If Terraform terminates the only web node during an apply, no deploy strategy saves you.
Three conditions must hold during an update:
- At least one healthy target serves traffic at every moment.
- New resources pass health checks before old ones drain.
- State changes are applied in small, reviewable plans—not one giant destroy-and-recreate.
The boundary matters. Terraform should not restart your database on every AMI bump unless you designed for it. Separate state files or workspaces for network, compute, and data tiers reduce blast radius. I cover workspace strategy in my notes on Terraform workspaces and environments.
How do you use Terraform lifecycle rules for zero-downtime replacements?
The lifecycle meta-argument is your primary lever. The create_before_destroy flag tells Terraform to stand up the replacement before destroying the old resource. Without it, many resources follow destroy-then-create order—and users see an outage window.
create_before_destroy on single instances
For a standalone EC2 instance behind an ALB target group, pin the lifecycle block on the instance resource:
resource "aws_instance" "web" {
ami = var.web_ami
instance_type = var.instance_type
subnet_id = aws_subnet.private.id
vpc_security_group_ids = [aws_security_group.web.id]
lifecycle {
create_before_destroy = true
}
tags = {
Name = "${var.project}-web"
}
}
resource "aws_lb_target_group_attachment" "web" {
target_group_arn = aws_lb_target_group.app.arn
target_id = aws_instance.web.id
port = 80
} When the AMI changes, Terraform creates a new instance first. You still must register it with the target group and confirm health before deregistering the old node. For deeper lifecycle behaviour, see Terraform lifecycle meta-arguments explained.
prevent_destroy on stateful resources
Databases and stateful volumes need the opposite guard. Block accidental deletion during a rushed apply:
resource "aws_db_instance" "primary" {
identifier = "${var.project}-mysql"
engine = "mysql"
engine_version = "8.4"
instance_class = "db.t3.medium"
lifecycle {
prevent_destroy = true
}
} Pair prevent_destroy with explicit runbooks for major version upgrades. MySQL 8.4 LTS remains the common managed choice; MySQL 9.7 exists but many teams stay on 8.4 LTS until providers certify it.
replace_triggered_by for controlled rollouts
Terraform 1.2+ supports replace_triggered_by. Use it when a related resource change should force a clean instance replacement:
resource "aws_instance" "web" {
ami = data.aws_ami.app.id
instance_type = "t3.small"
lifecycle {
create_before_destroy = true
replace_triggered_by = [
aws_launch_template.app.latest_version
]
}
} This pattern fits golden-AMI workflows. Build a new image, bump the launch template, and let Terraform roll forward without hand-SSHing servers.
How do rolling updates with autoscaling groups avoid downtime?
Single-instance setups work for small VPS workloads. Production traffic needs autoscaling groups (ASG) plus a load balancer. The ASG maintains desired capacity while instances cycle.
Configure the ASG with a launch template and an instance refresh or rolling update policy:
resource "aws_autoscaling_group" "web" {
name = "${var.project}-web-asg"
desired_capacity = 2
min_size = 2
max_size = 4
vpc_zone_identifier = aws_subnet.private[*].id
target_group_arns = [aws_lb_target_group.app.arn]
health_check_type = "ELB"
health_check_grace_period = 300
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 100
instance_warmup = 300
}
}
} Set min_healthy_percentage = 100 when you run at least two instances. A single-node ASG cannot satisfy that constraint—you need temporary over-provisioning or a maintenance window.
For sister sites on shared EC2 infrastructure, I combine this Terraform layer with zero-downtime Laravel deployment via Deployer. Terraform replaces the host. Deployer swaps the release on each healthy host.
Health check tuning
ALB health checks must match your app. A Laravel app might expose /health returning HTTP 200. Match the interval, threshold, and timeout to boot time:
resource "aws_lb_target_group" "app" {
name = "${var.project}-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/health"
healthy_threshold = 2
unhealthy_threshold = 3
timeout = 5
interval = 30
matcher = "200"
}
} Official AWS guidance on target group health checks lives in the AWS Application Load Balancer documentation. Tune values against real boot metrics—not defaults copied from a tutorial.
Which Terraform update strategies compare for production uptime?
Not every change needs the same pattern. Pick the strategy based on blast radius, rollback speed, and cost.
| Strategy | Best for | Downtime risk | Rollback speed | Cost impact |
|---|---|---|---|---|
create_before_destroy | Single instances, ENIs, IPs | Low if health-checked | Medium—re-apply previous code | Low |
| ASG rolling refresh | Web fleets, stateless workers | Very low at N≥2 | Fast—revert launch template | Medium during overlap |
| Blue/green (dual ASG) | High-traffic APIs, eCommerce peaks | Very low | Fast—flip listener rule | High—double capacity briefly |
| In-place update | Tags, SG rules, DNS TTL | None for many types | Instant | None |
| Replace (default order) | Nothing user-facing | High | Slow | Low |
Blue/green at the infrastructure layer mirrors application blue/green. Stand up aws_autoscaling_group.green, shift the ALB listener, then destroy blue in a later apply. I use this shape on booking platforms like Adventure Third Pole Trek where trek season traffic spikes are predictable.
In-place updates suit security group rules, Route53 record TTL changes, and tag-only diffs. They never trigger replacement. Know which attributes force replacement in the provider schema before you plan.
How do you run Terraform safely in CI/CD without causing outages?
Manual terraform apply from a laptop is how production breaks. Pipeline discipline separates teams that sleep from teams that page at 2 a.m.
- Pin provider versions in
required_providers—see Terraform provider version pinning. - Run
terraform fmt -checkandterraform validateon every push. - Post the plan output to the merge request. Block apply on unexpected destroys.
- Store state remotely with locking—documented in managing Terraform state safely.
- Apply from CI only on protected branches after approval.
- Run drift detection on a schedule—see Terraform drift detection strategies.
A GitLab CI job I use on shared infrastructure follows this shape:
plan:
stage: plan
script:
- terraform init -input=false
- terraform plan -input=false -out=plan.tfplan
artifacts:
paths:
- plan.tfplan
apply:
stage: apply
when: manual
script:
- terraform init -input=false
- terraform apply -input=false plan.tfplan Scan plans for destroy actions. Tools like Checkov catch misconfigurations before apply—covered in scanning Terraform with Checkov. Pair IaC scans with application checks using a JSON formatter when you parse plan output in scripts.
Separate pipelines for infrastructure and application code clarify ownership. My write-up on GitOps for infrastructure vs application explains why mixing them in one job creates confusion during rollbacks.
State locking and partial failure
Remote backends—S3 plus DynamoDB on AWS, or Terraform Cloud—prevent concurrent applies. If an apply fails mid-roll, state reflects reality. Do not delete state entries by hand.
Run terraform plan again after any failure. Terraform reconciles drift and tells you what remains. For module-heavy repos, reusable blocks in Terraform modules for reusable infrastructure keep rollouts consistent across staging and production.
What infrastructure changes still cause downtime even with Terraform?
Terraform cannot erase physics. Some resources always interrupt service unless you architect around them.
Single points of failure. One EC2 instance, one PHP-FPM box, one database with no replica—any replacement drops traffic. Minimum two web nodes behind a load balancer is the baseline for zero downtime.
Stateful database major upgrades. Engine version jumps on RDS often need failover windows. Use read replicas, test on a snapshot clone, and schedule cutovers. Application migrations need the patterns in Laravel migrations best practices for zero downtime and database migrations at scale.
DNS TTL and certificate changes. Lower TTL before swapping load balancer endpoints. ACM certificate validation must complete before you attach a new cert to the listener.
Security group replacement. Some SG attribute changes force replacement. Attach the new group before removing the old one, or use separate rules resources where the provider allows in-place edits.
Provider bugs and drift. Unpinned providers can change behaviour between plan and apply. Drift from manual console edits causes surprise destroys. Scheduled terraform plan -refresh-only runs surface drift early.
The HashiCorp Terraform lifecycle documentation lists every meta-argument behaviour. Read it before you rely on a blog snippet for production.
How do you connect Terraform infrastructure updates to application deploys?
Zero downtime is a full-stack contract. Terraform must finish before Deployer—or your CI runner—pushes code to new hosts.
A practical sequence on legal-tech portals and eCommerce stacks I maintain:
- Apply Terraform changes that add capacity or refresh AMIs.
- Wait until all new targets report healthy in the load balancer.
- Run application deploy to sync code and run migrations on each node.
- Drain and terminate old infrastructure only after traffic shifts.
On Court Marriage In Nepal and similar Laravel properties, database migrations run in expand-contract phases. Infrastructure and schema changes must not race each other.
If your team lacks in-house DevOps capacity, structured Linux system administration and support and maintenance services cover Terraform pipelines plus Deployer rollouts on Ubuntu 22/24 with PHP 8.3 or 8.4.
For greenfield platforms, enterprise application development should include IaC from day one—not a post-launch panic when the first AMI ages out.
Hosting choices affect how much Terraform you need. A single VPS from a domain and hosting package may never justify ASGs. A multi-AZ AWS stack for Quick And Easy Nepalese Grocery does. Match tooling to scale.
Read more from my background on full-stack production work since 2010 or browse the wider blog archive for Terraform and deployment topics.
Key Takeaways
- Use
create_before_destroyand ASG rolling refresh for stateless tiers; never rely on default destroy-then-create order. - Require load balancer health checks that match real app boot time before deregistering old nodes.
- Run plan-only CI on every merge; apply manually or through approved pipelines with remote state locking.
- Pin provider versions, scan with Checkov, and schedule drift detection to prevent surprise replacements.
- Pair Terraform infra rollouts with application deploy tools and expand-contract migrations on stateful apps.
- Accept maintenance windows for single-node or major database upgrades until you add redundancy.
People Also Ask
Can Terraform update EC2 instances without downtime?
Yes, when at least two instances sit behind a load balancer and you use create_before_destroy or an ASG instance refresh with min_healthy_percentage = 100. A single instance cannot update with zero downtime unless you temporarily add a second node.
What is the difference between Terraform rolling update and blue/green?
Rolling updates replace instances one or few at a time within one ASG. Blue/green runs two full fleets and switches traffic at the load balancer. Rolling costs less. Blue/green rolls back faster when validation fails on the green stack.
Does create_before_destroy guarantee zero downtime?
No. It only changes replacement order. You still need health checks, enough capacity, and correct target group registration so traffic never routes to an unready instance.
How do you roll back a failed Terraform infrastructure update?
Revert the merged code to the last known-good commit and run a new plan-apply cycle. For blue/green, flip the listener back to the old ASG. Never edit state by hand unless you are recovering from a documented disaster scenario.
Ship infrastructure changes without waking your users
Zero-Downtime Infrastructure Updates with Terraform come down to redundancy, lifecycle rules, health-checked cutover, and disciplined CI—not a single HCL flag. Start with two nodes, pin providers, and review every destroy in the plan output.
Need Terraform pipelines wired to your Laravel or WordPress stack on AWS or VPS infrastructure? Contact us for a production review, or explore recent work in the portfolio and related guides on zero-downtime PHP deployment with Deployer.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

