EC2 — Virtual Servers
Amazon Elastic Compute Cloud (EC2) provides resizable virtual servers in the cloud. EC2 is the workhorse of AWS — web servers, batch jobs, bastion hosts, and container nodes all run on EC2 unless you choose a fully managed alternative.
How EC2 Works
When you launch an instance, AWS provisions a VM on shared hardware in your chosen AZ. You select an AMI (Amazon Machine Image), instance type, storage, and networking. You pay per second (Linux/Windows) for running instances.
Launch Request → AMI + Instance Type + Subnet + SG → Running Instance
↓
EBS Volume (persistent)
Launch an Instance (CLI)
# Find a current Amazon Linux 2023 AMI
aws ec2 describe-images \
--owners amazon \
--filters "Name=name,Values=al2023-ami-2023*" "Name=architecture,Values=x86_64" \
--query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
--output text
# Launch instance
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \
--instance-type t3.micro \
--key-name my-key \
--security-group-ids sg-0123456789abcdef0 \
--subnet-id subnet-0123456789abcdef0 \
--iam-instance-profile Name=EC2-S3-Profile \
--metadata-options "HttpTokens=required" \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=web-server},{Key=Environment,Value=dev}]' \
--count 1
Console path: EC2 → Launch Instance → Amazon Linux 2023 → t3.micro → Create key pair → Select VPC/subnet → Configure security group → Launch.
Connect via SSH
chmod 400 my-key.pem
ssh -i my-key.pem ec2-user@<public-ip>
# Amazon Linux 2023 default user: ec2-user
# Ubuntu AMI default user: ubuntu
# Debian AMI default user: admin
Session Manager (No SSH Key Required)
# Requires SSM agent + IAM role with AmazonSSMManagedInstanceCore
aws ssm start-session --target i-0123456789abcdef0
Prefer Session Manager in production — no open port 22, full audit trail in CloudTrail.
Instance Types
| Family | vCPU/RAM Profile | Use Case | Example |
|---|---|---|---|
| t3/t4g | Burstable | Dev/test, low-traffic web | t3.micro (Free Tier) |
| m7i/m7g | Balanced | General web apps, APIs | m7i.large |
| c7i/c7g | Compute-heavy | Batch, transcode, gaming | c7i.xlarge |
| r7i/r7g | Memory-heavy | Caches, in-memory DBs | r7i.2xlarge |
| g5/p4 | GPU | ML training/inference | g5.xlarge |
Use the AWS Instance Type Explorer to compare. Graviton (t4g, m7g) instances offer 20–40% better price-performance for compatible workloads.
Storage Options
| Type | Persistence | Performance | Use Case |
|---|---|---|---|
| EBS gp3 | Persistent | Configurable IOPS/throughput | Boot volumes, databases |
| EBS io2 | Persistent | Highest IOPS | Mission-critical DBs |
| Instance Store | Ephemeral | NVMe, very fast | Temp caches, scratch |
| EFS | Shared, persistent | NFS protocol | Multi-instance shared files |
# Create and attach an additional EBS volume
aws ec2 create-volume --size 100 --volume-type gp3 --availability-zone us-east-1a
aws ec2 attach-volume --volume-id vol-xxx --instance-id i-xxx --device /dev/sdf
# On the instance:
sudo mkfs -t xfs /dev/xvdf
sudo mkdir /data && sudo mount /dev/xvdf /data
Security Groups
Stateful firewalls attached to instances:
# Allow HTTP and HTTPS from anywhere
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp --port 443 --cidr 0.0.0.0/0
# Allow SSH only from your office IP
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp --port 22 --cidr 203.0.113.50/32
Best practice: Application servers in private subnets; load balancer in public subnets. No direct SSH from 0.0.0.0/0.
Auto Scaling Groups
Automatically maintain desired capacity and replace unhealthy instances:
# 1. Create launch template
aws ec2 create-launch-template \
--launch-template-name web-lt \
--launch-template-data '{
"ImageId": "ami-0c55b159cbfafe1f0",
"InstanceType": "t3.small",
"SecurityGroupIds": ["sg-xxx"],
"IamInstanceProfile": {"Name": "EC2-AppRole"}
}'
# 2. Create Auto Scaling Group
aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name web-asg \
--launch-template LaunchTemplateName=web-lt,Version=1 \
--min-size 2 --max-size 10 --desired-capacity 2 \
--vpc-zone-identifier "subnet-aaa,subnet-bbb" \
--target-group-arns arn:aws:elasticloadbalancing:us-east-1:123:targetgroup/web/xxx \
--health-check-type ELB
# 3. Scale on CPU
aws autoscaling put-scaling-policy \
--auto-scaling-group-name web-asg \
--policy-name scale-on-cpu \
--policy-type TargetTrackingScaling \
--target-tracking-configuration '{
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ASGAverageCPUUtilization"
},
"TargetValue": 60.0
}'
User Data (Bootstrap Scripts)
Run commands on first boot:
aws ec2 run-instances \
--image-id ami-xxx \
--instance-type t3.micro \
--user-data file://bootstrap.sh \
...
#!/bin/bash
# bootstrap.sh
yum update -y
yum install -y docker
systemctl start docker
systemctl enable docker
docker run -d -p 80:80 nginx:latest
Real-World Scenario: Three-Tier Web App
| Tier | EC2 Config | Notes |
|---|---|---|
| Web (ASG) | 2× t3.small across 2 AZs | Behind ALB, private subnets |
| App (ASG) | 2× m7i.large | No public IP, SG allows ALB only |
| Bastion | 1× t3.micro | Public subnet, SSH restricted to VPN IP |
Database runs on RDS (not EC2) for managed backups and patching.
EC2 vs Alternatives
| Option | When to Choose |
|---|---|
| EC2 | Full OS control, legacy apps, custom networking |
| Lambda | Event-driven, short-lived, no server management |
| ECS/Fargate | Containerized apps without managing EC2 |
| Elastic Beanstalk | Quick deploy of web apps with minimal config |
Common Mistakes
- Running production on t3.micro — burstable credits exhaust under sustained load
- No Auto Scaling — manual capacity management doesn’t survive traffic spikes
- Public instances with default SG — exposes all ports internally
- Forgetting EBS snapshots — instance store data is lost on stop/terminate
- IMDSv1 enabled — use IMDSv2 (
HttpTokens=required) to prevent SSRF credential theft
Troubleshooting
| Issue | Check | Fix |
|---|---|---|
| Cannot SSH | SG, NACL, key pair, public IP | Verify port 22 allowed from your IP |
| Instance status checks failed | OS crash, full disk | Reboot; check /var/log/messages via SSM |
| High CPU, throttled | t3 burst credits exhausted | Upgrade to m-family or enable unlimited mode |
| Cannot reach internet | No NAT Gateway in private subnet | Add NAT GW or use public subnet with EIP |
Best Practices
- Use launch templates (not launch configurations)
- Deploy across multiple AZs for high availability
- Attach IAM roles instead of embedding access keys
- Enable detailed monitoring for production ASGs
- Use gp3 EBS — cheaper and more configurable than gp2
- Stop (not terminate) dev instances overnight to save costs
- Patch AMIs regularly with EC2 Image Builder
Next: S3 — Object Storage.