AWS EC2

By Vishvesh Patel · DevOps EngineerUpdated September 12, 2026

How EC2 instances actually work. Families and sizing, pricing models, EBS vs instance store, IMDSv2, Session Manager instead of SSH, and stop vs terminate.

Amazon EC2

EC2 gives you virtual machines. That sounds unremarkable until you consider that the decisions around them, which family, which pricing model, which storage, how you get a shell, account for a large share of most AWS bills and most AWS security findings.

EC2: Instance Lifecycle

AMI
base image
launch
pending
running
stop
stopped
EBS Volume
persistent disk
Security Group
stateful firewall
Key Pair
SSH access

Stopped instances retain their EBS root volume. You only stop paying for compute

Reading an instance type

m6i.xlarge decomposes into four parts:

PartMeans
mFamily (general purpose)
6Generation: higher is newer, faster and usually cheaper per unit
iProcessor: i Intel, a AMD, g Graviton (ARM)
xlargeSize within the family
FamilyFor
tBurstable (dev, low-traffic services)
mBalanced general purpose
cCompute optimised (CPU-bound work, build agents)
r, xMemory optimised (databases, caches)
i, dStorage optimised (local NVMe)
p, g, infGPU and ML accelerators

Two easy wins hide in that table. Always use the newest generation available. An m7i typically costs less than an m5 and performs better. And Graviton instances are roughly 20% cheaper for equivalent performance; if your workload is a container, an interpreted language or anything with an ARM build, the migration is often trivial and the saving is permanent.

Burstable instances have a credit system

t family instances earn CPU credits while below their baseline and spend them above it. Run out and the instance is throttled hard, sometimes to 20% of a core, which presents as an application that is inexplicably slow while CloudWatch shows low CPU.

aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 --metric-name CPUCreditBalance \
  --dimensions Name=InstanceId,Value=i-0abc123 \
  --start-time 2026-09-11T00:00:00Z --end-time 2026-09-12T00:00:00Z \
  --period 3600 --statistics Average

unlimited mode lets them burst past the balance for an extra charge, which is fine until a runaway process produces a surprising bill. For anything with sustained load, an m family instance is both cheaper and more predictable.

Pricing

ModelDiscountTrade-off
On-DemandNoneMaximum flexibility
Savings PlansUp to ~72%1 or 3 year spend commitment
Reserved InstancesUp to ~72%Commitment tied to instance attributes
SpotUp to ~90%Can be reclaimed with 2 minutes' notice
Dedicated Hosts()Licensing and compliance requirements

Compute Savings Plans are the pragmatic default for steady workloads: they commit to an hourly spend rather than a specific instance type, so they keep applying as you change families, sizes and regions. Reserved Instances are more rigid and usually only worth it for a workload you are certain will not change.

Spot is the largest single saving available and is underused. Anything fault-tolerant. CI runners, batch jobs, stateless web tiers behind a load balancer, Kubernetes worker nodes. Can run on Spot with a mixed-instances policy that falls back to On-Demand. Handle the two-minute interruption notice and the risk is manageable.

Storage: EBS and instance store

EBS is network-attached block storage that persists independently of the instance.

TypeUse
gp3The default. Baseline 3,000 IOPS and 125 MB/s, independent of size
gp2Legacy. IOPS scale with size (migrate to gp3)
io2High, provisioned IOPS for demanding databases
st1, sc1Throughput-optimised HDD for large sequential data

Moving gp2 volumes to gp3 is typically around 20% cheaper and faster, because gp3 no longer ties performance to volume size. It is a live change with no downtime:

aws ec2 modify-volume --volume-id vol-0abc123 --volume-type gp3

Instance store is physically attached NVMe. It is far faster and entirely ephemeral: stopping the instance loses it permanently. Use it for caches, scratch space and temporary build output, never for anything you need to keep.

Two EBS defaults worth changing: enable encryption by default at the account level, and note that the root volume is set to delete on termination while additional volumes are not.

Getting a shell: use Session Manager, not SSH

The traditional approach, a key pair, port 22 open, a bastion host, has a lot of moving parts to get wrong. SSM Session Manager replaces all of it:

aws ssm start-session --target i-0abc123

No open inbound ports, no key pairs to distribute or rotate, no bastion to maintain. Access is controlled by IAM, every session is logged to CloudTrail, and it works for instances in private subnets with no public IP at all.

It needs the SSM agent (preinstalled on current Amazon Linux and Ubuntu AMIs) and an instance profile with AmazonSSMManagedInstanceCore. That is the whole setup.

If you still need SSH, at least restrict the security group to a known CIDR rather than 0.0.0.0/0. An open port 22 is found by automated scanners within minutes.

User data and metadata

User data runs on first boot:

#!/bin/bash
dnf install -y nginx
systemctl enable --now nginx

It runs once, as root, on the first start only. A common confusion when people expect it on every reboot. Output goes to /var/log/cloud-init-output.log, which is the first place to look when an instance comes up wrong.

The instance metadata service provides instance identity and, importantly, the temporary credentials from its IAM role. Require IMDSv2:

aws ec2 modify-instance-metadata-options \
  --instance-id i-0abc123 \
  --http-tokens required \
  --http-endpoint enabled

IMDSv1 answered a plain GET, which meant a server-side request forgery bug in your application could be used to read the instance's AWS credentials. IMDSv2 requires a session token obtained by PUT, which closes that path. New instances default to requiring it; older ones need this change.

Stop, terminate, hibernate

ActionInstanceRoot EBSInstance storePublic IP
StopHalted, no compute chargeKept, still billedLostLost unless Elastic IP
TerminateGoneDeleted by defaultLostLost
HibernateRAM saved to diskKeptLostLost unless Elastic IP

Two things catch people. A stopped instance still bills for its EBS volumes. Stopping saves the compute charge only. And a stopped instance gets a new public IP when restarted, which breaks anything pointing at the old one; attach an Elastic IP, or better, put a load balancer in front.

Enable termination protection on anything that matters:

aws ec2 modify-instance-attribute --instance-id i-0abc123 --disable-api-termination

Treat instances as replaceable

The pattern that saves the most operational pain: define the instance in a launch template, bake or bootstrap it reproducibly, and put it in an Auto Scaling group even if the desired capacity is one.

Then a failed instance is replaced automatically rather than repaired by hand at 3am, and nothing has state that only exists on one machine.

Go deeper in this section

Sub-topicWhat you will learn
Launching an InstanceAMIs, launch options and first boot
Instance TypesChoosing families and sizes
Security GroupsStateful firewall rules and tiered access
Key PairsSSH keys and safer alternatives

Quick reference

TaskCommand
List running instancesaws ec2 describe-instances --filters Name=instance-state-name,Values=running
Shell without SSHaws ssm start-session --target <id>
Stop / startaws ec2 stop-instances --instance-ids <id>
Terminateaws ec2 terminate-instances --instance-ids <id>
Require IMDSv2aws ec2 modify-instance-metadata-options --instance-id <id> --http-tokens required
gp2 to gp3aws ec2 modify-volume --volume-id <id> --volume-type gp3
Console outputaws ec2 get-console-output --instance-id <id>
Latest Amazon Linux AMIaws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --query Parameter.Value

Key takeaways

  • Use the newest generation, and consider Graviton: both are cheaper and faster for most workloads.
  • t instances throttle when credits run out, which looks like a mysterious slowdown at low reported CPU.
  • Compute Savings Plans for steady load, Spot for anything fault-tolerant. Spot is the biggest saving most teams ignore.
  • Migrate gp2 to gp3: cheaper and faster, changed live with no downtime.
  • Use SSM Session Manager instead of SSH. No open ports, no keys, IAM-controlled and fully audited.
  • Require IMDSv2, which closes the SSRF path to your instance's AWS credentials.
  • Stopping still bills for EBS, and restarting gives a new public IP.

Check your understanding

Exercise

Slow at low CPU

A t3.medium web server becomes very slow under moderate load, but CloudWatch shows CPU utilisation around 20%. What is the likely cause?

Exercise

Why IMDSv2 matters

What class of vulnerability does requiring IMDSv2 on an EC2 instance mitigate?


Frequently Asked Questions

How do I choose an EC2 instance type?

Start with the family that matches the bottleneck, m for balanced workloads, c when CPU-bound, r when memory-bound, t only for genuinely bursty or low-traffic work. Then pick the newest generation available, since newer generations are typically both faster and cheaper. Consider Graviton (g in the name, such as m7g) for around 20% saving where your workload runs on ARM, which most containerised and interpreted applications do. Size from measurement rather than estimate, and use Compute Optimizer, which recommends right-sizing from real utilisation data.

What is the difference between stopping and terminating an instance?

Stopping halts the instance and ends compute charges, but the root EBS volume is preserved and still billed, and you can start it again. Terminating destroys the instance and, by default, deletes its root volume permanently. Two details surprise people regularly: a stopped instance still costs money for its storage, and restarting a stopped instance assigns a new public IP unless an Elastic IP is attached. Data on instance store volumes is lost on stop as well as on terminate.

How do I connect to an EC2 instance without SSH keys?

Use SSM Session Manager: aws ssm start-session --target i-0abc123. It needs the SSM agent, which is preinstalled on current Amazon Linux and Ubuntu AMIs, and an instance profile with the AmazonSSMManagedInstanceCore policy. There are no inbound ports to open, no keys to distribute or rotate, and no bastion host. Access is granted through IAM and every session is recorded in CloudTrail. It works for instances in private subnets with no public IP at all.

How can I reduce my EC2 costs?

In order of impact: buy Compute Savings Plans for baseline steady-state usage, which discount up to around 72% while remaining flexible across instance families and regions. Move fault-tolerant workloads, CI runners, batch processing, stateless web tiers, Kubernetes nodes, to Spot for up to 90% off. Migrate to Graviton where the workload supports ARM. Upgrade to the newest instance generation and convert gp2 volumes to gp3. Then find idle and oversized instances with Compute Optimizer, and stop non-production instances outside working hours.

What is IMDSv2 and should I enforce it?

The instance metadata service provides instance identity and the temporary credentials from the instance's IAM role. IMDSv1 served these over a plain GET request, which meant a server-side request forgery flaw in your application could be used to read those credentials and act as the instance in AWS. IMDSv2 requires a session token obtained via PUT, which closes that route. Enforce it, aws ec2 modify-instance-metadata-options --http-tokens required, on every instance. New instances default to requiring it, but older ones do not.

Why doesn't my user data script run when I restart the instance?

Because user data executes only on the first boot by design. Cloud-init records that it has run and skips it thereafter, so changes to the script have no effect on an existing instance. For configuration that must apply on every boot, use a systemd unit or a configuration management tool instead. When a first boot does not produce the expected result, /var/log/cloud-init-output.log on the instance contains the script's output and is almost always where the answer is.

Continue Learning

Explore Related Topics

When it breaks

Related Resources