AWS EC2 Complete Guide: Instances, Types, Auto Scaling (2026)

Amazon EC2 (Elastic Compute Cloud) is the backbone of most AWS architectures. Whether you're running a web application, a batch job, or a machine learning workload, understanding how to choose the right instance type, configure it securely, and scale it automatically will directly impact your cost and reliability. This guide covers everything from instance families to Auto Scaling Groups with target tracking policies — the practical stuff you'll actually use on the job.

Instance Types and Families

EC2 instance types follow a naming convention: family + generation + attributes + size. For example, m6i.xlarge = general-purpose family (m), 6th generation (6), Intel processor (i), extra-large size.

FamilyUse CaseCommon Types
t3 / t4gBurstable, dev/test, small web appst3.micro, t3.medium, t4g.small
m6i / m7gGeneral-purpose, balanced CPU/RAMm6i.xlarge, m7g.2xlarge
c6g / c7gCompute-intensive, CI/CD, APIsc6g.large, c7g.4xlarge
r6g / r7gMemory-intensive, databases, cachingr6g.xlarge, r7g.2xlarge
p4d / g5GPU, ML training/inferencep4d.24xlarge, g5.xlarge
i3en / im4gnStorage-optimized, high I/Oi3en.xlarge, im4gn.large
Pro Tip: Graviton3-based instances (c7g, m7g, r7g) offer up to 40% better price-performance than x86 equivalents. If your app runs on Linux and doesn't need Windows-specific deps, benchmark with Graviton first.

The t3/t4g burstable instances use CPU credits. When idle they accumulate credits; under load they spend them. Once credits are exhausted, CPU is throttled to the baseline. For production workloads with consistent load, use t3.unlimited mode or switch to an m-family instance.

Pricing Models: Spot, On-Demand, Reserved

Choosing the wrong pricing model is one of the top causes of AWS bill shock. Here's a practical breakdown:

ModelDiscount vs On-DemandBest ForRisk
On-Demand0%Dev/test, unpredictable trafficNone, but most expensive
Reserved (1yr, no upfront)~35%Steady-state prod workloadsCommitment risk
Reserved (3yr, all upfront)~60%Databases, baseline computeLong commitment
Savings Plans (Compute)~66%Flexible across families/regions$/hr commitment
Spot~70–90%Batch jobs, stateless workers, CI2-min termination notice
Note: Spot Instances can be reclaimed by AWS with a 2-minute warning. Always use Spot for stateless, fault-tolerant workloads. For a mixed fleet, use Auto Scaling with a mix of On-Demand base capacity + Spot additional capacity.

A practical pattern: set your Auto Scaling Group to use 1 On-Demand base instance + Spot for the rest with multiple instance types in a mixed instances policy. This gives you fault tolerance while cutting costs by 60–80%.

# CloudFormation: Mixed Instances Policy
MixedInstancesPolicy:
  InstancesDistribution:
    OnDemandBaseCapacity: 1
    OnDemandPercentageAboveBaseCapacity: 0
    SpotAllocationStrategy: capacity-optimized
  LaunchTemplate:
    LaunchTemplateSpecification:
      LaunchTemplateId: !Ref MyLaunchTemplate
      Version: $Latest
    Overrides:
      - InstanceType: m6i.large
      - InstanceType: m5.large
      - InstanceType: m5a.large
      - InstanceType: m6a.large

Launch Templates and AMIs

Launch Templates replace the older Launch Configurations and support versioning, which is critical for rolling updates. They define everything about how an instance launches: AMI, instance type, key pair, security groups, IAM profile, user data, and more.

# Create a launch template via AWS CLI
aws ec2 create-launch-template \
  --launch-template-name my-app-template \
  --version-description "v1 - initial" \
  --launch-template-data '{
    "ImageId": "ami-0abcdef1234567890",
    "InstanceType": "m6i.large",
    "IamInstanceProfile": {"Name": "my-ec2-profile"},
    "SecurityGroupIds": ["sg-0123456789abcdef0"],
    "UserData": "IyEvYmluL2Jhc2gKc3VkbyB5dW0gdXBkYXRlIC15",
    "TagSpecifications": [{
      "ResourceType": "instance",
      "Tags": [{"Key": "Name", "Value": "my-app"}]
    }]
  }'

AMIs (Amazon Machine Images) are the blueprint for your instances. Use AWS-provided AMIs for standard OS installs, but create golden AMIs for production: bake your app dependencies, agents (CloudWatch, SSM), and config into a custom AMI so instances boot faster and are always in a known state.

Pro Tip: Use EC2 Image Builder to automate golden AMI creation. Define a pipeline that installs packages, runs hardening scripts, tests the image, and publishes it. Trigger builds on AWS-published AMI updates for automatic OS patching.

User Data Scripts and Instance Profiles

User data runs once on first boot as root. Use it for bootstrapping that can't be baked into an AMI — like pulling app config from Parameter Store or registering with a service.

#!/bin/bash
# User data script: install app, configure CloudWatch agent
set -e

# Install SSM and CloudWatch agents
yum install -y amazon-ssm-agent amazon-cloudwatch-agent
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent

# Pull config from Parameter Store
APP_VERSION=$(aws ssm get-parameter \
  --name /myapp/version \
  --query Parameter.Value \
  --output text \
  --region us-east-1)

# Deploy app
aws s3 cp s3://my-artifacts/myapp-${APP_VERSION}.jar /opt/myapp/app.jar
systemctl enable myapp
systemctl start myapp

Instance Profiles are the correct way to give EC2 instances AWS permissions — never put access keys on an instance. An instance profile wraps an IAM role and attaches it to the instance. The instance can then call any AWS service that the role permits, with credentials auto-rotated by the metadata service.

# CLI: create role, attach policy, create instance profile
aws iam create-role \
  --role-name ec2-app-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{"Effect": "Allow", "Principal": {"Service": "ec2.amazonaws.com"}, "Action": "sts:AssumeRole"}]
  }'

aws iam attach-role-policy \
  --role-name ec2-app-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

aws iam create-instance-profile --instance-profile-name ec2-app-profile
aws iam add-role-to-instance-profile \
  --instance-profile-name ec2-app-profile \
  --role-name ec2-app-role

SSM Session Manager (No SSH Keys)

Session Manager lets you connect to EC2 instances through the AWS console or CLI without opening port 22, without managing SSH keys, and with full audit logging. This is the modern, secure way to access instances.

Requirements: The instance needs the SSM Agent (pre-installed on Amazon Linux 2+, Windows Server 2016+) and the AmazonSSMManagedInstanceCore IAM policy attached via its instance profile.

# Start a session from your terminal (no SSH needed)
aws ssm start-session --target i-0123456789abcdef0

# Run a command on multiple instances without logging in
aws ssm send-command \
  --document-name "AWS-RunShellScript" \
  --targets "Key=tag:Environment,Values=production" \
  --parameters '{"commands":["sudo systemctl restart myapp"]}' \
  --output-s3-bucket-name my-ssm-logs

# Port-forward for local debugging (tunnel port 8080 from instance)
aws ssm start-session \
  --target i-0123456789abcdef0 \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["8080"],"localPortNumber":["8080"]}'
Note: All Session Manager sessions are logged to CloudWatch Logs and S3 (if configured). This gives you a complete audit trail of who ran what — a requirement for most compliance frameworks (SOC2, PCI, HIPAA).

Placement Groups

Placement groups control how instances are distributed across the underlying hardware:

  • Cluster: All instances on the same rack. Maximum network throughput (up to 100 Gbps between instances). Use for HPC, low-latency applications. Risk: single rack failure takes everything down.
  • Spread: Each instance on distinct hardware. Maximum fault isolation. Limit: 7 instances per AZ per group. Use for small sets of critical instances.
  • Partition: Instances divided into partitions; partitions use separate racks. Good for distributed systems like Kafka, Cassandra, HDFS where you want rack awareness.
# Create a cluster placement group for HPC workloads
aws ec2 create-placement-group \
  --group-name hpc-cluster \
  --strategy cluster

# Launch instances into it
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type c6gn.16xlarge \
  --placement '{"GroupName": "hpc-cluster"}' \
  --count 4

Auto Scaling Groups with Target Tracking

Auto Scaling Groups (ASGs) automatically add or remove instances based on demand. Target tracking is the simplest and most effective scaling policy: you set a target metric value (e.g., 60% CPU), and AWS continuously adjusts capacity to maintain that target.

{
  "AutoScalingGroupName": "my-app-asg",
  "MinSize": 2,
  "MaxSize": 20,
  "DesiredCapacity": 4,
  "VPCZoneIdentifier": "subnet-abc,subnet-def,subnet-ghi",
  "LaunchTemplate": {
    "LaunchTemplateId": "lt-0123456789abcdef0",
    "Version": "$Latest"
  },
  "HealthCheckType": "ELB",
  "HealthCheckGracePeriod": 300,
  "TargetGroupARNs": ["arn:aws:elasticloadbalancing:..."],
  "Tags": [
    {"Key": "Environment", "Value": "production", "PropagateAtLaunch": true}
  ]
}
# Apply a target tracking policy: maintain 60% average CPU
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-app-asg \
  --policy-name cpu-target-tracking \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "TargetValue": 60.0,
    "ScaleInCooldown": 300,
    "ScaleOutCooldown": 60
  }'
Pro Tip: Set a shorter scale-out cooldown (60s) and longer scale-in cooldown (300s). You want to scale out fast when traffic spikes, but scale in slowly to avoid terminating instances during a brief dip that's about to bounce back. Also always set HealthCheckType: ELB so the ASG uses actual load balancer health — not just instance reachability — to replace unhealthy instances.

For more advanced patterns, use predictive scaling to scale before traffic arrives based on historical patterns, combined with target tracking for real-time adjustment. Also consider lifecycle hooks to run initialization or drain logic (e.g., deregister from a service mesh) before instances are added to or removed from the fleet.

Frequently Asked Questions

What's the difference between a Launch Template and a Launch Configuration?

Launch Configurations are the older, immutable mechanism — once created, they can't be edited. Launch Templates support versioning, so you can create a new version and roll it out gradually. They also support Spot + On-Demand mixed fleets, which Launch Configurations don't. AWS recommends Launch Templates for all new work.

When should I use Reserved Instances vs Savings Plans?

Compute Savings Plans are generally more flexible — they apply to any EC2 instance family, size, OS, and region, and they also cover Fargate and Lambda. Reserved Instances give slightly higher discounts (up to 60% for 3yr all-upfront vs ~66% for Savings Plans) but are locked to a specific instance family and region. If your workload might change instance types over 3 years, Savings Plans are safer.

How do I prevent accidental Spot termination from breaking my app?

Design for statelessness: store state in S3, DynamoDB, or ElastiCache rather than on instance storage. Use ASG lifecycle hooks and a Spot interruption handler (Lambda listening to EventBridge Spot interruption events) to drain connections gracefully. Always run at least 2 instances so one Spot termination doesn't cause a full outage.

What's the best way to SSH into an instance in a private subnet?

Don't SSH at all — use SSM Session Manager. It works in private subnets as long as the instance has outbound HTTPS access to the SSM endpoints (or you configure VPC endpoints for SSM). No bastion host, no key management, and you get full audit logs. If you must SSH, use an EC2 Instance Connect Endpoint (free, GA since 2023) instead of a bastion.

How do I speed up Auto Scaling response time?

The main levers are: (1) Use a pre-baked golden AMI so instances don't spend 3–5 minutes installing packages on boot. (2) Set a short scale-out cooldown (60s). (3) Enable Warm Pools to keep a pool of pre-initialized instances in Stopped state, ready to launch in seconds. (4) For Java apps, consider GraalVM native images or use container-based deployments with ECS/Fargate for faster startup.