AWS Auto Scaling
What actually scales on AWS and how. EC2 Auto Scaling vs Application Auto Scaling, scaling ECS, DynamoDB and Aurora, and why right-sizing usually beats scaling.
"Auto Scaling" on AWS is not one service. It is two APIs with similar names that people routinely confuse, plus a handful of services that scale themselves.
| Service | What it scales | API |
|---|---|---|
| EC2 Auto Scaling | EC2 instances in an ASG | autoscaling |
| Application Auto Scaling | ECS services, DynamoDB, Aurora replicas, Lambda concurrency, EMR, Spot Fleet | application-autoscaling |
| AWS Auto Scaling (plans) | A console wrapper over both | autoscaling-plans |
If a CLI command or IAM policy is not working, checking which of these you are actually using is usually the fastest fix, aws autoscaling put-scaling-policy and aws application-autoscaling put-scaling-policy are different commands with different arguments.
Scale out, not up
Two ways to handle more load. Vertical scaling means a bigger instance: simple, requires no application changes, and has a ceiling plus downtime to resize. Horizontal scaling means more instances: no ceiling, no downtime, and it requires the application to tolerate multiple copies.
Auto Scaling on AWS means horizontal, and that imposes one real prerequisite: your application must be stateless. Sessions in memory, uploads on local disk, or a cache on the instance all break the moment a second instance appears or the first is replaced. Put sessions in ElastiCache or a cookie, uploads in S3, and state in a database. Reaching for sticky sessions instead works, and leaves you with uneven load and sessions lost on every scale-in.
Scaling EC2
An Auto Scaling group maintains a count of instances between a floor and a ceiling:
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name web-asg \
--launch-template LaunchTemplateName=web-lt,Version='$Latest' \
--min-size 2 --max-size 10 --desired-capacity 2 \
--vpc-zone-identifier "subnet-0a1b2c3d,subnet-4e5f6a7b" \
--target-group-arns "$TG_ARN" \
--health-check-type ELB --health-check-grace-period 180
Even with no scaling policy at all, that group is doing useful work: it replaces failed instances and keeps capacity spread across availability zones. Self-healing is arguably the bigger benefit, and it is the one people forget when they say they "don't need auto scaling".
Then attach a scaling policy to change the desired count automatically.
Scaling ECS
Container workloads are where auto scaling genuinely shines, because a task starts in seconds rather than the minutes an EC2 instance needs. Registration comes first, then the policy:
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/prod-cluster/api \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 2 --max-capacity 20
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/prod-cluster/api \
--scalable-dimension ecs:service:DesiredCount \
--policy-name cpu-target --policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 60.0,
"PredefinedMetricSpecification": {"PredefinedMetricType": "ECSServiceAverageCPUUtilization"}
}'
Forgetting register-scalable-target is the most common mistake here. A policy created without it fails, and on EC2 there is no equivalent step, which is exactly why people trip over it when moving to ECS.
On EKS the equivalents are the Horizontal Pod Autoscaler for pods and Karpenter or Cluster Autoscaler for nodes, both of which sit outside these APIs.
Scaling DynamoDB
Only relevant in provisioned mode, on-demand scales by itself:
aws application-autoscaling register-scalable-target \
--service-namespace dynamodb \
--resource-id table/Orders \
--scalable-dimension dynamodb:table:WriteCapacityUnits \
--min-capacity 5 --max-capacity 500
A caveat worth knowing before relying on it: DynamoDB auto scaling reacts over minutes, so a sudden spike throttles before capacity arrives. If your traffic is genuinely spiky, on-demand is the better answer despite the higher per-request price.
Scaling Aurora and Lambda
Aurora scales read replicas, not writes. The writer is a single instance, so read scaling is all this offers:
aws application-autoscaling register-scalable-target \
--service-namespace rds \
--resource-id cluster:prod-aurora \
--scalable-dimension rds:cluster:ReadReplicaCount \
--min-capacity 1 --max-capacity 8
Lambda scales concurrency automatically. What you configure is provisioned concurrency to remove cold starts, and reserved concurrency to cap a function. The latter also protecting a downstream database from being overwhelmed by thousands of simultaneous invocations.
Choose the right metric
CPU is the default and frequently the wrong signal. It works for compute-bound work and tells you nothing about a service that is slow because it is waiting on a database.
| Workload | Better metric |
|---|---|
| CPU-bound API | CPUUtilization |
| I/O-bound web tier | ALBRequestCountPerTarget |
| Queue consumer | Queue depth per instance, or oldest message age |
| Latency-sensitive | TargetResponseTime |
ALBRequestCountPerTarget is under-used and often the best choice for a web tier: requests per instance maps directly to work arriving, regardless of whether that work is CPU-heavy.
For an SQS consumer, scale on a custom metric of messages per instance. Scaling a queue consumer on CPU means it scales up only once it is already overloaded.
Scaling is not a substitute for right-sizing
Auto scaling handles variable load. It does not fix an application that needs eight times the resources it should, and it makes over-provisioning more expensive by multiplying it.
Before adding a scaling policy, check whether the instance type actually matches the workload. A memory-bound service on a compute-optimised instance scales out on CPU that is never the constraint. Compute Optimizer reads real utilisation and recommends types, and acting on that often removes the need to scale at all.
Equally, scaling only saves money if it scales in. A group whose minimum is set to peak capacity is a fixed fleet with extra machinery.
Common errors
ValidationException: No scalable target registered for service namespace: ecs,
resource ID: service/prod-cluster/api
Missing register-scalable-target. Register first, then create the policy.
ValidationError: Desired capacity 15 is above max capacity 10
Set the ceiling first; desired cannot exceed max.
Alarm in ALARM state, nothing happens. Usually a cooldown still in effect, desired already at max, or a service quota on that instance type in the region.
Quick reference
| Task | Command |
|---|---|
| Create an EC2 group | aws autoscaling create-auto-scaling-group |
| Register an ECS target | aws application-autoscaling register-scalable-target |
| Policy (EC2) | aws autoscaling put-scaling-policy |
| Policy (ECS/DynamoDB) | aws application-autoscaling put-scaling-policy |
| See scaling history | aws autoscaling describe-scaling-activities |
| List registered targets | aws application-autoscaling describe-scalable-targets |
| Change bounds | aws autoscaling update-auto-scaling-group --min-size … --max-size … |
Key takeaways
- There are two APIs.
autoscalingis EC2 only;application-autoscalingis everything else. register-scalable-targetcomes first for ECS, DynamoDB and Aurora. EC2 has no equivalent.- Horizontal scaling requires a stateless application. Sticky sessions are a workaround, not a fix.
- An ASG with no scaling policy is still valuable: it replaces failed instances.
- CPU is often the wrong metric.
ALBRequestCountPerTargetsuits web tiers; queue depth suits consumers. - Aurora scales readers, not writers.
- DynamoDB auto scaling reacts in minutes: use on-demand for spiky traffic.
- Right-size before you scale, and make sure it scales in or you have saved nothing.
Check your understanding
A policy that will not create
Creating a target tracking policy for an ECS service fails with "No scalable target registered for service namespace: ecs". The same approach worked for an EC2 Auto Scaling group. What is different?
A consumer that scales too late
An SQS consumer fleet scales on average CPU at 70%. Queue depth grows for twenty minutes during bursts before instances are added. What is the better approach?
Frequently Asked Questions
What is the difference between EC2 Auto Scaling and Application Auto Scaling?
EC2 Auto Scaling manages EC2 instances inside an Auto Scaling group, using the autoscaling API. Application Auto Scaling is a separate service, using the application-autoscaling API, that scales everything else. ECS service task counts, DynamoDB provisioned capacity, Aurora read replicas, Lambda provisioned concurrency, Spot Fleet and more. They have similarly named commands with different arguments, and Application Auto Scaling requires you to register a scalable target before attaching any policy, which EC2 does not.
Do I need Auto Scaling if my traffic is constant?
Probably, though not for scaling. An Auto Scaling group also replaces instances that fail health checks and keeps capacity distributed across availability zones, so a group with min, max and desired all set to the same number still gives you self-healing and zone balance that a manually launched instance does not. Many teams run exactly that configuration deliberately. The scaling policy is a separate decision you can add later.
What metric should I scale on?
Whatever actually correlates with the work arriving. CPU suits compute-bound services and is misleading for anything that spends its time waiting on a database. For a web tier, ALBRequestCountPerTarget is usually better. Requests per instance maps directly to load. For a queue consumer, use backlog per instance or oldest-message age, since CPU only rises once the fleet is already behind. For latency-sensitive services, TargetResponseTime reflects the thing users actually experience.
Why isn't my Auto Scaling group scaling even though the alarm is firing?
Check three things in order. A cooldown or scaling activity still in progress will suppress further action, describe-scaling-activities shows the history and any reason given. The desired capacity may already be at max-size, which silently caps everything. And a service quota on that instance type in the region can prevent launches, which appears as a failed scaling activity rather than an alarm problem. The scaling activity description almost always names the cause.
Does auto scaling save money?
Only if it scales in as readily as it scales out. A group whose minimum equals peak capacity is a fixed fleet with extra moving parts, and it will not save anything. The larger saving usually comes earlier, from right-sizing: a workload on an inappropriate instance family scales out on a metric that was never the real constraint. Check Compute Optimizer's recommendations before adding a policy. Fixing the instance type sometimes removes the need to scale at all.
Can Auto Scaling scale an instance vertically?
No. EC2 Auto Scaling changes how many instances exist, never how large they are, and resizing an instance requires stopping it. If you need a different size, update the launch template and use an instance refresh to roll the group onto the new type. Vertical scaling on AWS is a deliberate change rather than an automatic one. The services that scale a single resource's capacity automatically are the managed ones such as DynamoDB, Aurora Serverless and Lambda.