Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Zero-Downtime Infrastructure Updates with Terraform

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.

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.
Zero-Downtime Stack LayersUsers / DNSLoad BalancerHealth checks gate trafficOld InstanceDraining connectionsNew InstancePasses health checkTerraform State + CI PlanControls ordered replacement
Zero-Downtime Infrastructure Updates with Terraform require healthy targets behind a load balancer while state-driven replacement proceeds.

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.

ASG Rolling Replacement FlowStep 1Plan + approveStep 2Launch new EC2Step 3Health check OKStep 4Drain old nodeLaunch Template Changeinstance_refresh or rolling policyALB keeps routing to healthy targetsMin healthy 100% during single-AZ small fleets
Rolling ASG updates are the standard pattern for Zero-Downtime Infrastructure Updates with Terraform at scale.

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.

StrategyBest forDowntime riskRollback speedCost impact
create_before_destroySingle instances, ENIs, IPsLow if health-checkedMedium—re-apply previous codeLow
ASG rolling refreshWeb fleets, stateless workersVery low at N≥2Fast—revert launch templateMedium during overlap
Blue/green (dual ASG)High-traffic APIs, eCommerce peaksVery lowFast—flip listener ruleHigh—double capacity briefly
In-place updateTags, SG rules, DNS TTLNone for many typesInstantNone
Replace (default order)Nothing user-facingHighSlowLow

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.

Blue/Green Infrastructure CutoverApplication Load BalancerBlue ASGCurrent production100% traffic todayGreen ASGNew AMI / configValidate, then switchListener rule flip in TerraformDestroy blue ASG in a follow-up apply
Blue/green ASGs give the safest Zero-Downtime Infrastructure Updates with Terraform when rollback must take seconds, not minutes.

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.

  1. Pin provider versions in required_providers—see Terraform provider version pinning.
  2. Run terraform fmt -check and terraform validate on every push.
  3. Post the plan output to the merge request. Block apply on unexpected destroys.
  4. Store state remotely with locking—documented in managing Terraform state safely.
  5. Apply from CI only on protected branches after approval.
  6. 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.

Choose Your Update StrategyResource change?Stateless + N ≥ 2Stateful / N = 1ASG rolling refreshor blue/green ASGMaintenance windowor add redundancy firstAlways: plan review + health checksHashiCorp lifecycle docs for edge cases
Decision guide for Zero-Downtime Infrastructure Updates with Terraform when instance count and statefulness differ.

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:

  1. Apply Terraform changes that add capacity or refresh AMIs.
  2. Wait until all new targets report healthy in the load balancer.
  3. Run application deploy to sync code and run migrations on each node.
  4. 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_destroy and 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

Zero downtime at the infrastructure layer means user requests keep succeeding while compute, networking, or storage changes underneath. Terraform handles the platform; application deploy tools like Deployer handle code swaps. Three conditions must hold: at least one healthy target serves traffic at every moment, new resources pass health checks before old ones drain, and state changes are applied in small reviewable plans—not one giant destroy-and-recreate. Separate state files or workspaces for network, compute, and data tiers reduce blast radius.

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.

The create_before_destroy lifecycle meta-argument is your primary lever—it tells Terraform to stand up the replacement before destroying the old resource. Pin it on EC2 instances registered with ALB target groups, then confirm health before deregistering old nodes. For databases and stateful volumes, use prevent_destroy to block accidental deletion during rushed applies. Terraform 1.2+ supports replace_triggered_by to force clean instance replacement when a launch template version bumps in golden-AMI workflows.

No. It only changes replacement order from destroy-then-create to create-then-destroy. You still need load balancer health checks, enough capacity, and correct target group registration so traffic never routes to an unready instance.

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.

Configure an ASG with a launch template, target_group_arns, health_check_type = ELB, and instance_refresh using strategy Rolling. Set min_healthy_percentage = 100 and instance_warmup = 300 so replacements cycle while capacity stays healthy. The ASG maintains desired capacity during the refresh. A single-node ASG cannot satisfy min_healthy_percentage = 100—you need at least two instances, temporary over-provisioning, or an accepted maintenance window. On shared EC2 stacks I maintain, this Terraform layer pairs with zero-downtime Laravel deployment via Deployer.

Match strategy to blast radius, rollback speed, and cost. Use create_before_destroy for single instances behind a load balancer; ASG rolling refresh for stateless web fleets at N≥2; blue/green when rollback must take seconds, such as high-traffic booking platforms during predictable peaks. In-place updates suit tags, security group rules, and DNS TTL changes with no replacement. Default destroy-then-create order should never touch user-facing resources. Read the provider schema to know which attributes force replacement before you plan.

Pin provider versions in required_providers, run terraform fmt -check and terraform validate on every push, and post plan output to merge requests. Store state remotely with locking—S3 plus DynamoDB or Terraform Cloud. Apply from CI only on protected branches after approval, using terraform plan -out=plan.tfplan then terraform apply plan.tfplan. Scan plans for destroy actions with Checkov. Schedule drift detection and run terraform plan -refresh-only to surface manual console edits. Separate infrastructure and application pipelines so rollbacks stay clear during incidents.

Single points of failure—one EC2 instance or one database with no replica—drop traffic during any replacement. Stateful RDS major engine version jumps often need failover windows; test on snapshot clones and schedule cutovers. DNS TTL and ACM certificate validation need lead time before swapping load balancer endpoints. Some security group attribute changes force replacement—attach the new group before removing the old one. Unpinned providers and drift from manual console edits cause surprise destroys between plan and apply. Accept maintenance windows for single-node stacks until you add redundancy.

Match health checks to real application boot time, not copied defaults. A Laravel app might expose /health returning HTTP 200. Configure path, healthy_threshold, unhealthy_threshold, timeout, interval, and matcher on aws_lb_target_group against measured boot metrics—typical starting values are healthy_threshold 2, unhealthy_threshold 3, timeout 5, interval 30, matcher 200. Align ASG health_check_grace_period and instance_warmup with the same duration so new targets are not marked unhealthy before PHP-FPM finishes starting on Ubuntu 22/24 hosts.

Zero downtime is a full-stack contract. Apply Terraform changes that add capacity or refresh AMIs first, wait until all new load balancer targets report healthy, then run application deploy via Deployer or your CI runner to sync code and run migrations on each node. Drain and terminate old infrastructure only after traffic shifts. On Laravel legal-tech portals I maintain, database migrations run in expand-contract phases so infrastructure and schema changes do not race each other during the same maintenance window.

Revert the merged code to the last known-good commit and run a new plan-apply cycle through your CI pipeline. For blue/green deployments, flip the ALB listener back to the old ASG immediately. Never edit state by hand or delete state entries after a partial failure—run terraform plan again and let Terraform reconcile what remains. Remote state locking prevents concurrent applies from corrupting recovery while you investigate the failed rollout.

Apply prevent_destroy on stateful resources like aws_db_instance where accidental deletion during a rushed apply would be catastrophic. Pair it with explicit runbooks for major version upgrades rather than relying on Terraform alone to manage engine jumps. The article uses MySQL 8.4 as the managed choice—many teams stay on 8.4 LTS until providers certify newer versions. prevent_destroy blocks destroy actions in plan output but does not stop you from scheduling controlled upgrades through documented cutover steps.

Setting min_healthy_percentage = 100 tells the ASG to keep the full healthy fleet online while instances cycle during a rolling refresh. Combined with at least two nodes behind an ALB, this ensures user requests always hit a passing health check. If you run fewer instances than the constraint allows, the refresh stalls or forces downtime. Production web tiers should treat two nodes behind a load balancer as the baseline, not an optional luxury for zero-downtime AMI or launch template updates.

Rolling updates within one ASG carry medium cost impact from brief instance overlap during refresh—generally cheaper than running two complete fleets simultaneously. Blue/green infrastructure updates carry high cost impact because you briefly double capacity while both ASG stacks run, but rollback is fast when validation fails on the green stack. I use blue/green on booking platforms with predictable traffic spikes where instant listener rollback outweighs the temporary compute bill; everyday AMI bumps on stateless web tiers suit rolling refresh at N≥2.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: