Set Up a Multi-AZ App

Learn to set up a multi-AZ high-availability app on AWS. Step-by-step walkthrough, troubleshooting, and next steps for Python developers.

Focus: set up a multi-az high-availability app

Sponsored

You've built a great app, deployed it to a single EC2 instance, and it's working beautifully — until that instance fails and your users get a 504 Gateway Timeout. Uptime is a feature, and in AWS, the cheapest, most reliable way to keep your app online is to replicate it across multiple Availability Zones (AZs). This lesson shows you exactly how to set up a multi-AZ high-availability app using EC2, an Application Load Balancer (ALB), and Auto Scaling — the same architecture that powers production services at scale.

The problem this lesson solves

A single EC2 instance is a single point of failure. Hardware dies, software crashes, and AWS performs routine maintenance that can reboot your instance. When that happens, your app goes down — and every minute of downtime costs revenue, trust, and user frustration.

Even if your instance stays healthy, a sudden traffic spike can overwhelm its CPU or memory, turning a working app into a slow, unresponsive one. You need redundancy so that no single component is critical to the whole system. You need scalability so that demand growth is handled automatically. And you need automatic failover so that when one component fails, another takes over with zero manual intervention.

This is the problem that a multi-AZ architecture solves. By spreading your application across multiple isolated data centers within a region, you protect against both hardware failures and availability-zone-level outages, and you gain the ability to scale out — or in — on demand.

Core concept / mental model

Think of a region as a city, and an Availability Zone as a distinct power grid and network hub within that city. AWS isolates AZs from each other so that a fire, flood, or power outage in one AZ doesn't affect another. If you place your application in three AZs, you can lose one entire grid and still stay fully online.

Your architecture is like a three-legged stool:

  • Availability Zones (AZs): isolated locations within a region (e.g., us-east-1a, us-east-1b, us-east-1c). They share a low-latency network connection but have independent power, cooling, and physical security.
  • Application Load Balancer (ALB): the traffic director. It sits in front of your app and distributes incoming requests across healthy instances in all AZs.
  • Auto Scaling Group (ASG): the workforce manager. It launches and terminates instances based on demand, and automatically replaces any instance that fails health checks.

Here's the flow: a user hits your domain -> Route 53 resolves it to your ALB's DNS name -> the ALB receives the request and forwards it to one of the EC2 instances in your Auto Scaling group. The ASG continually monitors instance health and adjusts the number of instances to match demand and maintain desired capacity.

Pro tip: An AZ is not the same as a data center — AWS may call it a data center, but each AZ can consist of multiple physical data centers. The key concept is isolation: fault isolation is what makes multi-AZ HA possible.

How it works step by step

Here's the end-to-end process, broken into logical stages:

1. Prepare your AMI and launch template

Before you can scale, you need a Golden AMI — a pre-configured machine image that includes your app code, dependencies, and startup scripts. You create a launch template that specifies this AMI, the instance type, security groups, and optionally a user-data script that runs on boot.

2. Create a target group

The target group defines the set of instances that your load balancer will route traffic to. You configure a health check path (e.g., /health) that the ALB uses to decide if an instance is alive.

3. Create an Application Load Balancer (ALB)

Your ALB is registered with the target group. You must enable cross-zone load balancing so that traffic is evenly distributed across instances in all AZs, not just within a single AZ.

4. Create an Auto Scaling group and attach it to the ALB

Your ASG uses the launch template to launch instances across at least two AZs. You set a desired capacity (e.g., 2 instances), a minimum (e.g., 2 for HA), and a maximum (e.g., 10). A scaling policy (target tracking) automatically adds or removes instances based on CPU utilization or request count.

5. Test failover

Terminate an instance manually, and the ASG will launch a replacement in seconds. The ALB will send traffic only to healthy instances during the transition. That's true HA.

Hands-on walkthrough

Let's put this into practice. We'll use the AWS CLI, which you should have configured with credentials and a default region.

Step 1: Create a launch template

Create a file launch-template.json with a minimal configuration. This example uses an Amazon Linux 2 AMI (adjust the AMI ID to your region):

{
  "ImageId": "ami-0abcdef1234567890",
  "InstanceType": "t3.micro",
  "KeyName": "my-keypair",
  "SecurityGroupIds": ["sg-0123456789abcdef0"],
  "UserData": "#!/bin/bash\nyum update -y\nyum install -y httpd\nsystemctl enable httpd\nsystemctl start httpd\necho '<html><h1>Multi-AZ App</h1></html>' > /var/www/html/index.html\necho 'ok' > /var/www/html/health"
}

Now register the launch template:

aws ec2 create-launch-template \
    --launch-template-name my-app-template \
    --launch-template-data file://launch-template.json

Step 2: Create a target group and ALB

# Create a target group for HTTP on port 80 (AZs are selected later)
aws elbv2 create-target-group \
    --name my-app-tg \
    --protocol HTTP --port 80 \
    --vpc-id vpc-0123456789abcdef0 \
    --health-check-path /health
# Capture TargetGroupArn from output
TG_ARN=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-app-tg/abcdef123456

# Create the ALB in at least two public subnets (different AZs)
aws elbv2 create-load-balancer \
    --name my-app-alb \
    --subnets subnet-aaaa subnet-bbbb subnet-cccc \
    --security-groups sg-0123456789abcdef0
# Capture LoadBalancerArn
LB_ARN=arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-app-alb/abcdef123456

Step 3: Create the Auto Scaling group and attach it to the ALB

# Create ASG
aws autoscaling create-auto-scaling-group \
    --auto-scaling-group-name my-app-asg \
    --launch-template LaunchTemplateName=my-app-template \
    --min-size 2 --max-size 6 --desired-capacity 2 \
    --vpc-zone-identifier "subnet-aaaa,subnet-bbbb,subnet-cccc" \
    --target-group-arns $TG_ARN

# Attach the target group to the ALB (actually you do this at ALB creation via listener)
# Create a listener that forwards traffic to the target group
aws elbv2 create-listener \
    --load-balancer-arn $LB_ARN \
    --protocol HTTP --port 80 \
    --default-actions Type=forward,TargetGroupArn=$TG_ARN

Step 4: Set up a target tracking scaling policy

aws autoscaling put-scaling-policy \
    --auto-scaling-group-name my-app-asg \
    --policy-name cpu-target \
    --policy-type TargetTrackingScaling \
    --target-tracking-configuration '{"PredefinedMetricSpecification":{"PredefinedMetricType":"ASGAverageCPUUtilization"},"TargetValue":60.0}'

Step 5: Test the setup

Get your ALB's DNS name and curl it:

ALB_DNS=$(aws elbv2 describe-load-balancers --names my-app-alb --query 'LoadBalancers[0].DNSName' --output text)
curl http://$ALB_DNS/

Expected output:

<html><h1>Multi-AZ App</h1></html>

Now simulate a failure: terminate one instance, then watch the ASG replace it:

aws ec2 terminate-instances --instance-ids i-0123456789abcdef0
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names my-app-asg

You'll see the ASG launch a new instance to maintain desired capacity. Your app stays up throughout — that's multi-AZ HA in action.

Pro tip: Always put your health check on an endpoint that performs a lightweight check (like database connectivity or cache ping). The ALB will automatically remove unhealthy instances from the rotation.

Compare options / when to choose what

Approach How it Works Pros Cons Best For
Single instance One EC2 instance Simple, cheap Single point of failure Tests, prototypes
Multi-AZ with ASG + ALB Instances spread across AZs behind a load balancer High availability, automatic recovery, auto scaling More complex, costs for load balancer and multiple instances Production apps that need uptime
Multi-region with Route 53 Replicate the whole stack in a second region, use Route 53 failover Survives a whole region outage Complex, expensive, data consistency issues Mission-critical global apps

When to choose what:

  • For development or a demo, a single instance is fine.
  • For production, always go with multi-AZ via ASG and ALB. The cost is low compared to the cost of downtime.
  • For disaster recovery (RPO/RTO in minutes), you might add a multi-region failover, but that's an advanced topic for later in this track.

Variations: alternatives to EC2 ASG

  • AWS Elastic Beanstalk — you just upload your app, and it manages the ASG, ALB, and scaling for you. Simpler but less control.
  • ECS / EKS with Fargate — if you containerize your app, you get multi-AZ scheduling without managing EC2 instances.
  • Lambda + API Gateway — serverless alternative; you don't manage instances at all, AWS handles availability.

Troubleshooting & edge cases

  • Health check failing but instance is healthy — your health check path may be wrong or the app might not respond on that path. Test with curl http://<instance-ip>/health from another machine. Also ensure the instance's security group allows traffic from the ALB (not the world) on the port.
  • ASG not launching instances across all AZs — you must specify subnets in multiple AZs in the ASG's VPC zone identifier. If you forget that, all instances go to one AZ, defeating HA.
  • Cross-zone load balancing disabled — traffic goes to instances only in the AZ where the ALB's node resides, leading to uneven loads. Enable it in the ALB listener's target group settings.
  • Instances restart repeatedly — check the user-data script's exit code. A non-zero exit code may cause the instance to be marked unhealthy. Look at system logs (/var/log/cloud-init-output.log).
  • Elastic IP association broken — after a failover, a new instance gets a new private IP. If your app relies on a hard-coded IP (e.g., for database access), it will break. Use the ALB DNS name or a service discovery mechanism instead.

What you learned & what's next

You learned how to set up a multi-AZ high-availability app on AWS by creating a launch template, a target group, an Application Load Balancer, and an Auto Scaling group with a scaling policy. You now understand how these components work together to provide redundancy, health checking, and automatic failover for your application. You also know how to compare multi-AZ with single-instance and multi-region architectures.

The next lesson in this track is [Next lesson topic, e.g., "Setting Up an Amazon RDS Multi-AZ Database"], where you'll apply a similar multi-AZ principle to your database layer, ensuring your entire data tier is also highly available.

Practice recap

Try modifying the launch template's user-data script to include a small Python Flask app instead of a static HTML page. After creating the ASG, create a scaling policy based on CPU utilization, then use a load-testing tool like hey or ab to generate traffic and watch the ASG scale out. Finally, terminate an instance and confirm your app remains available — that's the true test of a high-availability setup.

Common mistakes

  • Creating an Auto Scaling group without specifying subnets in multiple AZs — this defeats the purpose of HA and can cause all instances to stop if the single AZ goes down.
  • Forgetting to enable cross-zone load balancing on the ALB, which results in uneven traffic distribution when you have instances in multiple AZs.
  • Using an incorrect or missing health check path — the ALB will mark healthy instances as unhealthy and pull them out of rotation, causing 503 errors.
  • Hard-coding instance private IPs in your app (e.g., redis or DB connections) instead of using the ALB DNS or a service discovery that survives instance replacement.

Variations

  1. Use AWS Elastic Beanstalk to have AWS automatically create the ASG, ALB, and scaling policies for you — simplest but less fine-grained control.
  2. Containerize your app with Docker and run it on Amazon ECS with Fargate — you get multi-AZ scheduling without managing EC2 instances.
  3. Adopt a serverless approach with Lambda behind API Gateway to achieve high availability without thinking about infrastructure at all.

Real-world use cases

  • Running an e-commerce checkout API that can't go down during peak hours — any single instance failure would lose sales.
  • Deploying a SaaS application with an SLA that requires 99.9% uptime, where multi-AZ redundancy is a non-negotiable compliance requirement.
  • Scaling a web application that experiences unpredictable traffic spikes — auto scaling handles them automatically without manual intervention.

Key takeaways

  • A single EC2 instance is a single point of failure — protect your app with redundancy across multiple Availability Zones.
  • The ALB routes traffic only to healthy instances based on health checks, giving you automatic failover.
  • The Auto Scaling group replaces failed instances automatically and maintains a desired capacity for availability.
  • You must specify subnets in multiple AZs for your ASG and enable cross-zone load balancing for even traffic distribution.
  • Always test failover by terminating an instance and observing the ASG recover — don't assume HA works.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.