Laptop to Production

Auto scaling

Adding servers when busy, replacing them when they die.

Time
50 minutes
Level
Intermediate

Part 1: The problem

QuickCart, the first big promotion

QuickCart ran on two servers behind a load balancer. That was plenty for a normal evening.

Then marketing sent a push notification: free delivery tonight. Within minutes, orders were five times higher than usual. Both servers hit 100% CPU. Pages took ten seconds, then started timing out. Sam launched two more servers by hand and added them to the load balancer. By the time they were ready, the rush was over — and the two extra servers ran, unused, for the rest of the week.

A month later, one server died at 3am. Nobody noticed. The other server carried everything alone until Priya saw the alarm at breakfast.

Two problems, one cause: the number of servers was decided by a person, and people are slow, asleep, or busy.

QuickCart needs something that adds servers when it’s busy, removes them when it’s quiet, and replaces any that die. That’s an Auto Scaling group.

Part 2: The idea

An Auto Scaling group looks after a set of identical servers. You tell it how many you want. It keeps checking how many are running, and fixes any difference: launching a server when there are too few, terminating one when there are too many.

You stop managing servers one by one. You manage a number.

Key point: You don’t look after servers any more. You tell the group how many you want, and it keeps that true.

Three numbers

NumberWhat it meansQuickCart’s value
MinimumNever fewer than this, whatever happens2 — one in each zone
DesiredHow many there should be right now2 on a normal day. Scaling changes this number
MaximumNever more than this, however busy4 — a limit that protects the bill

Scaling never touches servers directly. It only changes desired. Then the group launches or terminates servers to match.

The recipe: a launch template

To launch a server, the group needs to know what kind. It reads a launch template: everything you’d choose when launching a server on EC2, saved once.

Part of the templateQuickCart uses
Image (AMI)Amazon Linux 2023
Instance typet3.micro
Security groupThe web servers’ group
Instance profileA role that allows Session Manager, so no SSH keys
Metadata versionIMDSv2 only
User dataThe start-up script that installs and starts the app

Launch templates are versioned. To change something — a newer image, say — you make version 2 and tell the group to use it. Version 1 stays, so going back is one click.

The template leaves out the subnet. The group picks subnets, because it decides where each server goes.

Spreading across zones

You give the group subnets in two or more availability zones. It keeps the servers balanced between them: with four servers and two zones, two in each.

If a whole zone fails, the servers there are lost. The group notices it’s short and launches replacements in the zones that still work. That’s why QuickCart’s minimum is 2: never fewer than one per zone.

How the group knows a server is broken

The group replaces any server it thinks is unhealthy. What “unhealthy” means depends on which checks you turn on:

Health checkAsksCatchesMisses
EC2 — always onIs the machine running?A server that stopped, was terminated, or failed its hardware checksAn app that crashed on a machine that’s still running
ELB — turn it on when you use a load balancerDoes the app answer on its health path?A crashed or stuck app—

With EC2 checks alone, a server whose app has crashed looks fine forever. It stays in the group, doing nothing useful. With a load balancer in front, turn on ELB health checks too, so the group replaces servers the load balancer has given up on.

The grace period

A new server needs time to boot and run its start-up script before its app can answer. For the health check grace period, the group ignores health checks on a new server. The default is 300 seconds.

Scaling: changing desired on its own

A scaling policy changes the desired number for you.

PolicyHow it worksUse it when
Target trackingYou pick a metric and a target, like average CPU 50%. The group adds or removes servers to stay near itAlmost always. Start here
Step scalingYou set thresholds: above 70%, add 2; above 90%, add 4You need finer control than target tracking gives
ScheduledAt a set time, set desired to a number: every day at 6pm, 4Busy times you can predict, like QuickCart’s dinner rush

Target tracking works like a thermostat. Behind the scenes it creates two CloudWatch alarms: one that adds servers when the metric stays above the target, and one that removes them when it stays well below. It adds quickly and removes slowly, so a short dip doesn’t throw away servers you’ll need again a minute later.

The instance warmup setting tells target tracking to ignore a brand-new server’s metrics for a while. A server that’s still booting shows low CPU, which would otherwise make the average look better than it is.

Terminating a server on purpose

Terminating a server from the EC2 console is a hard stop. The load balancer keeps sending it requests until its health checks notice, and any requests it was handling fail.

Asking the group to terminate it is gentler. The group first takes it out of the load balancer, waits for the deregistration delay so requests in progress can finish, and only then terminates it. The CLI is the clearest way to do this:

Terminal
aws autoscaling terminate-instance-in-auto-scaling-group --instance-id INSTANCE_ID --no-should-decrement-desired-capacity
PartWhat it means
aws autoscalingthe Auto Scaling part of the AWS CLI
terminate-instance-in-auto-scaling-groupask the group to remove one of its servers, draining it first
--instance-id INSTANCE_IDwhich server. Replace INSTANCE_ID with the ID from EC2 → Instances, like i-0123abcd
--no-should-decrement-desired-capacitykeep desired the same, so the group launches a replacement. Use --should-decrement-desired-capacity to shrink the group by one instead

To replace every server — after a new launch template version, for example — use Instance refresh on the group. It replaces servers in batches, waiting for each new one to pass its health checks. That’s a rolling release, done for you.

What this means for your app

Servers now come and go without warning. So nothing important can live on one server: not uploaded files (they go to S3), not orders (they go to the database), not a customer’s login session. Any server must be able to handle any request, and losing one must lose nothing.

Part 3: See it

Below is QuickCart’s group: two zones, minimum 2, maximum 4, target tracking at 50% CPU. Raise the traffic and watch the group add servers. Then terminate a server and watch it come back. Try the dinner rush to see what the maximum does.

Traffic
Average CPU
30%
Target
50%
Desired
2
In service
2
Min / max
2 / 4

Zone a

  • web 1 In service CPU 30%

Zone b

  • web 2 In service CPU 30%
ReadyTwo servers, one in each zone, handle a quiet afternoon. Raise the traffic, or terminate a server, and watch what the group does.

What to notice: a new server doesn’t help straight away. It boots, sits out its grace period, and only then takes traffic. That delay is why teams schedule extra servers before a rush they can predict, instead of waiting for CPU to climb.

Part 4: Do it

You’ll build a launch template and a group of two servers across two zones, reach a server through Session Manager, kill one and watch it come back, then load the servers until the group scales out. About 45 minutes, most of it watching.

This lab uses your account’s default VPC, which already has a public subnet in every zone. That keeps it cheap: no NAT gateway. The servers get public addresses so they can reach Systems Manager, but their security group lets nothing in. Check the region selector says N. Virginia (us-east-1).

  1. Create a security group for the servers

    In the AWS console: EC2Security GroupsCreate security group

    FieldEnter
    Security group namequickcart-web-sg
    DescriptionQuickCart web servers
    VPCthe default VPC
    Inbound rulesnone — delete any row the form adds
    Outbound rulesleave the default: All traffic to 0.0.0.0/0

    Choose Create security group.

    You should see

    The group, with 0 inbound rules and 1 outbound rule. Session Manager needs no inbound rule: the agent on the server connects out to Systems Manager.

  2. Create a role for Session Manager

    If you still have a role with AmazonSSMManagedInstanceCore from Servers on EC2, you can use that and skip this step.

    In the AWS console: IAMRolesCreate role

    FieldEnter
    Trusted entity typeAWS service
    Service or use caseEC2, then the use case EC2
    Permissions policiessearch for and tick AmazonSSMManagedInstanceCore
    Role namequickcart-web-role

    Choose Create role. The console also creates an instance profile with the same name — the wrapper that lets a server wear the role.

  3. Create the launch template

    In the AWS console: EC2Launch TemplatesCreate launch template

    FieldEnter
    Launch template namequickcart-web-lt
    Template version descriptionnginx with a health page
    Auto Scaling guidancetick Provide guidance to help me set up a template that I can use with EC2 Auto Scaling
    Application and OS ImagesQuick Start → Amazon Linux → Amazon Linux 2023 AMI, architecture 64-bit (x86)
    Instance typet3.micro
    Key pair nameDon’t include in launch template
    SubnetDon’t include in launch template — the group chooses
    Firewall (security groups)Select existing security group → quickcart-web-sg

    Then open Advanced details:

    FieldEnter
    IAM instance profilequickcart-web-role
    Detailed CloudWatch monitoringEnable — CPU every minute instead of every five, so scaling reacts sooner. It costs a little extra
    Metadata accessibleEnabled
    Metadata versionV2 only (token required)
    User datathe script below
    user-data.sh
    #!/bin/bash
    dnf install -y nginx
    echo "QuickCart web server" > /usr/share/nginx/html/index.html
    echo "ok" > /usr/share/nginx/html/health
    systemctl enable --now nginx
    LineWhat it does
    #!/bin/bashrun this script with bash. User data must start with this line, or it isn’t run as a script
    dnf install -y nginxinstall the nginx web server. -y answers yes, because nobody is there to type it
    echo "QuickCart web server" > /usr/share/nginx/html/index.htmlwrite a home page. > sends the text into that file
    echo "ok" > /usr/share/nginx/html/healthwrite a tiny page at /health for health checks to ask for
    systemctl enable --now nginxstart nginx now, and every time the server boots

    The script runs once, as root, on first boot. Choose Create launch template.

    You should see

    Successfully created quickcart-web-lt, with version 1.

  4. Create the Auto Scaling group

    In the AWS console: EC2Auto Scaling groupsCreate Auto Scaling group

    The wizard has several pages. The page names change from time to time; the fields are what matter.

    Choose launch template

    FieldEnter
    Auto Scaling group namequickcart-web-asg
    Launch templatequickcart-web-lt
    VersionDefault (1)

    Choose instance launch options

    FieldEnter
    VPCthe default VPC
    Availability Zones and subnetsthe default subnets in us-east-1a and us-east-1b
    Availability Zone distributionBalanced best effort

    Integrate with other services

    FieldEnter
    Load balancingNo load balancer
    Turn on Elastic Load Balancing health checksleave unticked — there’s no load balancer
    Health check grace period300 seconds

    Configure group size and scaling

    FieldEnter
    Desired capacity2
    Min desired capacity2
    Max desired capacity4
    Automatic scalingTarget tracking scaling policy
    Metric typeAverage CPU utilization
    Target value50
    Instance warmup120 seconds
    Instance maintenance policyNo policy

    Skip notifications. On Add tags, add Key Name, Value quickcart-web, so the servers have a name in the instance list. Then Create Auto Scaling group.

  5. Watch the group launch its servers

    In the AWS console: EC2Auto Scaling groupsquickcart-web-asgActivity

    The Activity history shows lines like
    Launching a new EC2 instance: i-0123abcd
    Cause: At 2026-09-25T10:02:14Z an instance was started in response to a difference between desired and actual capacity, increasing the capacity from 0 to 2.

    Your IDs and times will differ. Now open EC2 → Instances.

    You should see

    Two instances called quickcart-web, one in us-east-1a and one in us-east-1b, both Running. You didn’t launch either of them.

  6. Check the app from inside a server

    Wait a couple of minutes after the servers show Running, so the agent and the start-up script can finish.

    In the AWS console: EC2Instancesone quickcart-web instanceConnectSession ManagerConnect

    A terminal opens in your browser. Ask nginx for the health page:

    Terminal
    curl -s localhost/health
    PartWhat it means
    curlfetch a web address and print what comes back
    -ssilent: no progress bar
    localhost/healththe /health page on this same server
    You should see
    ok

    The user data ran, and no SSH key or open port was needed to check. Leave this tab open; you’ll use it in a later step.

  7. Kill a server and watch it come back

    In the AWS console: EC2Instancesthe other quickcart-web instanceInstance stateTerminate (delete) instance

    Confirm. Then go back to the group’s Activity tab and refresh it every half-minute or so.

    Within a minute or two, lines like
    Terminating EC2 instance: i-0456cdef
    Cause: an instance was taken out of service in response to an EC2 health check indicating it has been terminated or stopped.
    Launching a new EC2 instance: i-0789abcd
    Cause: an instance was started in response to a difference between desired and actual capacity, increasing the capacity from 1 to 2.
    You should see

    In EC2 → Instances, the terminated server and a brand-new one, launched in the same zone to keep the group balanced. That’s self-healing: nobody launched it.

  8. Load the servers and watch the group scale out

    To push the average CPU above 50%, load both servers. A t3.micro has two virtual CPUs, so you run two busy processes on each.

    In the Session Manager tab from before, run this twice:

    Terminal
    timeout 900 yes > /dev/null &
    PartWhat it means
    yesa tiny program that prints y forever, as fast as it can. It keeps one CPU at 100%
    > /dev/nullthrow the output away, so it doesn’t fill your screen
    timeout 900stop it automatically after 900 seconds (15 minutes), so you can’t forget it
    &run it in the background and give you the prompt back
    Each time, it prints a job number and a process ID, like
    [1] 4321

    Now connect to the other server the same way (Connect → Session Manager) and run the same command twice there too.

    Watch the group’s Activity tab. After about five minutes:

    A line like
    Cause: At 2026-09-25T10:31:40Z a monitor alarm TargetTracking-quickcart-web-asg-AlarmHigh-4f2c9e1a in state ALARM triggered policy Target Tracking Policy changing the desired capacity from 2 to 4.
    You should see

    Desired capacity: 4 on the group’s Details tab, and two new servers launching. With the new servers idle, the average CPU across all four comes back towards 50%. In CloudWatch → Alarms you’ll find the two alarms target tracking made for you: one AlarmHigh and one AlarmLow.

    After 15 minutes the yes processes stop by themselves. About 15 minutes after that, the low alarm lowers desired back to 2 and the group terminates two servers. You don’t need to wait for it — scaling in slowly is deliberate. Go on to clean up.

  9. Clean-up step: Clean up

    Order matters: the group first. Delete the servers first and the group just launches more.

    In the AWS console: EC2Auto Scaling groupsquickcart-web-asgActionsDelete

    Type delete and confirm. The group sets its capacity to 0, terminates every server, and then disappears. It takes a few minutes. The alarms target tracking made are deleted with it.

    You should see

    EC2 → Instances shows every quickcart-web server as Terminated, and the group is gone from the list.

    Then remove the rest:

    1. EC2 → Launch Templates → quickcart-web-lt → Actions → Delete template, and type Delete to confirm.
    2. EC2 → Security Groups → quickcart-web-sg → Actions → Delete security groups. If it says the group is in use, wait until every server has finished terminating.
    3. IAM → Roles → quickcart-web-role → Delete, unless you’re keeping it for the next lessons.

Part 5: Check yourself

An Auto Scaling group has min 2, desired 2 and max 4. You terminate one of its two servers from the EC2 console. What happens next?

The app on one server has crashed, but the server itself is still running. The group uses EC2 health checks only. What does the group do?

The app takes about 90 seconds to start. The group uses ELB health checks with a 30-second grace period. What happens?

Target tracking aims for 50% average CPU. Max is 4. A rush needs six servers' worth of CPU. What happens?

Why does QuickCart pick subnets in two availability zones for its group?

Part 6: Cheat sheet

On one card
TermIn one line
Auto Scaling groupKeeps the number of running servers equal to desired
Min / desired / maxNever fewer / right now / never more. Scaling only changes desired
Launch templateThe recipe for each server. Versioned; no subnet
ZonesGive the group subnets in two or more; it balances between them
EC2 health checkIs the machine running? Always on
ELB health checkDoes the app answer? Turn on when behind a load balancer
Grace periodTime before health checks count. Longer than real start-up time
Target trackingKeep a metric near a target, like average CPU 50%. Out fast, in slowly
Terminate gracefullyThrough the group, so it drains first
Instance refreshReplace every server in batches, for a new template version
Clean upDelete the group first. Deleting servers just makes it launch more

Finished the build and the quiz? Mark it done to fill in its stop on the route map.