Skip to content
ExponaBLOG
AWS FOR DEVELOPERS · DAY 083 / 100

Multi-AZ by default: what every AWS developer should know

AZ failures happen. Plan for them.

Expona Admin9 min read

Availability zone failures are not hypothetical—they happen regularly across AWS regions, and when they do, your system either survives gracefully or it doesn't. Building with Multi-AZ by default means accepting that any single zone can fail at any moment, and architecting your infrastructure so that failure is invisible to your users.

I've seen teams run their entire production stack in a single AZ because it seemed simpler. Then a power distribution unit fails, or AWS performs maintenance, and suddenly your service is offline. I'm writing this to help you avoid that path.

Why Multi-AZ Matters

AWS availability zones are physically separate data centers within the same region. They have independent power, cooling, and networking. When one fails, the others remain unaffected. But only if you've actually distributed your workload across them.

The harsh reality: a single-AZ failure will take down your entire application if you haven't planned for it. Not hypothetically. This has happened to companies across every industry. I've watched incident channels light up when a major platform went offline because they were running everything in us-east-1a.

Multi-AZ architecture isn't about perfection—it's about realistic risk management. You accept that failures happen, you design systems that survive them, and you verify that your design actually works.

At Least Two AZs for Every Tier

Every layer of your application should span at least two availability zones. This includes your API servers, background workers, caches, and any stateless service. The principle is straightforward: if one zone goes dark, the other carries the load.

Let me walk through a concrete example. Say you're running an e-commerce platform with a Node.js API tier. A single-AZ setup might look like this:

bash
// Bad: Single AZ
- EC2 instance in us-east-1a running your API
- Load balancer pointing only to that instance
- If us-east-1a fails, your API is offline

The corrected approach spans two AZs:

bash
// Good: Multi-AZ
- EC2 instance in us-east-1a running your API
- EC2 instance in us-east-1b running the same API
- Application Load Balancer distributing traffic across both
- Health checks ensure dead instances are removed quickly

In a Terraform configuration, this looks like:

bash
resource "aws_autoscaling_group" "api_asg" {
  name                = "api-asg"
  vpc_zone_identifier = [
    aws_subnet.api_subnet_1a.id,
    aws_subnet.api_subnet_1b.id
  ]
  min_size         = 2
  max_size         = 10
  desired_capacity = 2
  
  launch_template {
    id      = aws_launch_template.api.id
    version = "$Latest"
  }
  
  health_check_type         = "ELB"
  health_check_grace_period = 300
}

This setup ensures that even if us-east-1a becomes unavailable, your instances in us-east-1b continue serving traffic. The Auto Scaling Group automatically launches replacement instances in the remaining healthy AZ if the unhealthy one recovers.

The same principle applies to background workers, scheduled jobs, or any component processing traffic. Two AZs minimum. This is the floor, not the ceiling—some organizations spread across three, but two covers most failure scenarios.

Multi-AZ Databases

Databases are where many teams make their first Multi-AZ mistake. They either run a single database instance in one AZ and hope for the best, or they assume a replicated database is the same as a truly resilient one.

AWS offers explicit Multi-AZ deployments for RDS (PostgreSQL, MySQL, MariaDB, Oracle, SQL Server). When you enable Multi-AZ, AWS automatically provisions a standby replica in a different AZ, synchronously replicates writes, and orchestrates automatic failover if the primary fails.

Here's what that configuration looks like:

bash
resource "aws_db_instance" "postgres_primary" {
  identifier     = "production-db"
  engine         = "postgres"
  engine_version = "15.3"
  instance_class = "db.r6i.xlarge"
  
  allocated_storage = 100
  storage_type      = "gp3"
  storage_encrypted = true
  
  # Critical: Enable Multi-AZ
  multi_az = true
  
  db_name  = "proddb"
  username = "postgres"
  password = var.db_password
  
  # Backup configuration
  backup_retention_period = 30
  backup_window          = "03:00-04:00"
  preferred_maintenance_window = "sun:04:00-sun:05:00"
  
  # Network
  db_subnet_group_name   = aws_db_subnet_group.main.name
  publicly_accessible    = false
  vpc_security_group_ids = [aws_security_group.db.id]
  
  skip_final_snapshot = false
  final_snapshot_identifier = "production-db-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
}

When multi_az = true, AWS creates a standby instance in a different AZ. Your application connects to the primary through a DNS endpoint that automatically points to the healthy instance during failover. The failover typically takes 1-2 minutes—not instant, but far better than waiting for manual intervention.

For applications requiring sub-minute RTO (Recovery Time Objective), consider read replicas as part of a broader strategy. But understand that read replicas are not automatic failover—they're for scaling read traffic and geographic distribution. Multi-AZ is specifically for high availability.

DynamoDB handles this differently. It's inherently Multi-AZ by default—your data is automatically replicated across three AZs within a region. No configuration needed. This is one less thing to worry about.

For other databases like MongoDB, Redis, or Elasticsearch, verify the deployment model. If you're running them on EC2, you must manually implement replication and failover. If you're using managed services like ElastiCache or OpenSearch, enable Multi-AZ at creation time.

Balanced Capacity Per AZ

Multi-AZ only works if each zone has sufficient capacity to handle the full workload when other zones fail. Imbalanced deployments create a hidden risk: you've built Multi-AZ infrastructure that would actually fail if tested.

I've seen setups like this:

bash
// Bad: Imbalanced capacity
- 8 API instances in us-east-1a
- 2 API instances in us-east-1b

// During normal operations: traffic distributed across 10 instances
// If us-east-1a fails: remaining 2 instances must handle full load
// Result: cascading failures, timeouts, user-facing outages

The correct approach distributes capacity evenly:

bash
// Good: Balanced capacity
- 5 API instances in us-east-1a
- 5 API instances in us-east-1b

// If either AZ fails: remaining 5 instances handle full load
// This assumes your infrastructure can sustain full capacity on 50% of instances<p></p>

<p>Achieve this through Auto Scaling Group configuration:</p>

<pre><code class="language-bash">resource "aws_autoscaling_group" "api_asg" {
  # ... previous configuration ...
  
  vpc_zone_identifier = [
    aws_subnet.api_subnet_1a.id,
    aws_subnet.api_subnet_1b.id
  ]
  
  # Capacity balanced across zones
  desired_capacity         = 6
  min_size                 = 2
  max_size                 = 12
  
  # Force even distribution
  capacity_rebalance = true
  default_cooldown   = 300
  
  tag {
    key                 = "Name"
    value               = "api-server"
    propagate_at_launch = true
  }
}

The capacity_rebalance setting is crucial—it proactively replaces instances in zones that have less than their fair share of capacity, keeping distribution balanced during scaling operations.

For databases, balance means sizing your Multi-AZ standby to match the primary exactly. Not smaller, not larger. If you provision db.r6i.2xlarge for the primary but db.r6i.xlarge for the standby (to save costs), failover will degrade performance, and you've broken your Multi-AZ promise.

Load balancers should distribute traffic evenly across AZs. Most ALBs do this by default, but verify your configuration. Uneven distribution wastes capacity and reduces your failure tolerance.

Test by Draining an AZ

Reading about Multi-AZ and actually surviving an AZ failure are different things. The only way to know your design works is to test it under realistic conditions.

Set up a regular chaos engineering practice: drain an AZ and watch your system. This means deliberately taking all instances in one zone offline and verifying that the other zone handles the load gracefully.

Here's a practical test procedure:

  1. Document baseline metrics: response time, error rate, CPU, memory on remaining instances
  2. Identify all instances in us-east-1a using tags or filters
  3. Terminate them or scale them down
  4. Monitor for 5-10 minutes, looking for increased latency, errors, or cascading failures
  5. Restore the drained zone
  6. Analyze results and adjust capacity if needed

A concrete example using AWS CLI:

bash
# List instances in a specific AZ
aws ec2 describe-instances \
  --filters "Name=availability-zone,Values=us-east-1a" \
             "Name=tag:Environment,Values=production" \
  --query 'Reservations[].Instances[].InstanceId' \
  --output text

# Terminate them (or use Auto Scaling to drain)
aws ec2 terminate-instances --instance-ids i-xxxxx i-yyyyy i-zzzzz

# Monitor with CloudWatch
aws cloudwatch get-metric-statistics \
  --namespace AWS/ApplicationELB \
  --metric-name TargetResponseTime \
  --start-time 2024-01-15T10:00:00Z \
  --end-time 2024-01-15T10:10:00Z \
  --period 60 \
  --statistics Average

During the drain, watch these metrics closely:

  • API response time—should stay within acceptable bounds
  • Error rate—should remain near zero
  • CPU and memory on remaining instances—should spike but not saturate
  • Auto Scaling activity—should launch replacement instances quickly
  • Database connection pool usage—should spike but not exhaust

If any of these fail, you've discovered a gap. Maybe your remaining instances don't have enough capacity. Maybe your database connections are exhausted. Maybe your caching layer wasn't truly Multi-AZ. Fix it before real failure exposes the problem in production.

Run this test monthly, at minimum. More frequently if your infrastructure changes significantly. Make it part of your regular operations checklist, like backup testing.

Common Mistakes

Mistake 1: Multi-AZ databases without Multi-AZ compute. You've protected your data, but your API tier is still single-AZ. If the compute zone fails, your Multi-AZ database is unreachable.

Mistake 2: Cross-AZ replication without failover automation. You've set up read replicas across zones, but failover requires manual intervention. That's not Multi-AZ resilience—that's a slow backup strategy.

Mistake 3: Insufficient capacity planning for single-AZ operation. Your design looks balanced, but if one zone fails, the remaining instances are at 95% utilization. There's no buffer for spikes, and any minor surge causes cascading failures.

Mistake 4: Not testing failure scenarios. You've configured Multi-AZ, assumed it works, and never actually verified it. Real failures expose untested designs fast and painfully.

Mistake 5: Confusing regions with AZs. Some teams think running in two regions is sufficient Multi-AZ protection. Regions are useful for disaster recovery and geographic distribution, but Multi-AZ is about surviving zone-level failures within a single region. You need both.

Key Takeaways

  • Build Multi-AZ by default for every tier. This means at least two AZs for compute, stateless services, and explicit Multi-AZ for managed databases.
  • Balance capacity evenly across AZs so that losing any single zone doesn't cause cascading failures or performance degradation.
  • Use AWS-managed Multi-AZ features where available: RDS Multi-AZ, ElastiCache Multi-AZ, ALB cross-AZ distribution. They handle failover automatically.
  • Test AZ failures regularly. Drain one zone, verify system behavior, and adjust architecture based on results.
  • Multi-AZ isn't optional for production systems. It's the minimum acceptable resilience for any service users depend on.
  • Monitor failover behavior during tests and in production. Know your RTO and RPO for each component, and design accordingly.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles