☰ All modules Tier 3 · EC2 (instances, storage, ELB, Auto Scaling) GuideCheat sheet ‹ s3rds ›
Contents
  1. How EC2 appears on DVA-C02
  2. 1. IAM roles, instance profiles and the credential chain
  3. 2. Instance metadata service (IMDSv2)
  4. 3. User data and bootstrapping
  5. 4. Security groups and connectivity troubleshooting
  6. 5. Purchasing options and Spot
  7. 6. EBS volumes, snapshots, encryption and AMIs
  8. 7. Choosing storage: EBS vs EFS vs instance store
  9. 8. Elastic Load Balancing: ALB, NLB and GWLB
  10. 9. Sticky sessions, TLS certificates and SNI
  11. 10. Auto Scaling: policies, health checks and instance refresh
  12. 11. CloudWatch for EC2: metrics, agent and status checks
  13. Quick decision rules
  14. Changed vs older material

EC2 — DVA-C02 Mastery Guide

  • Sources: course chapter 02 (EC2), plus the IMDS and credential-chain slides of chapter 07 (CLI, SDK and policies) and the EC2 monitoring slides of chapter 13 (Monitoring). The course serves as the syllabus only; all wording here is original.
  • Verified against AWS documentation on 2026-10-12. The marker ⚠️ Changed: <current>. Older material may say <old>. flags a value or behaviour that differs from the course or older practice tests: learn the current value. Every change is listed in Changed vs older material and in notes/current-facts.md.
  • Tier 3 (supporting), bank target 40 questions. Domains: Troubleshooting and Optimization (primary), Development with AWS Services, Security, Deployment.
  • How to use it: /explain <topic> teaches from these sections (concept → exam facts → developer angle → traps → 3 check questions); /quiz ec2 asks bank questions whose source_note links point at the ## N. headings below; /drill ec2 fires short recall prompts from notes/ec2-cheat-sheet.md. Suggested loop: read a section and answer its practice questions before opening the answers → /explain what is still unclear → /quiz ec2 → /drill ec2.

How EC2 appears on DVA-C02

DVA-C02 does not test EC2 as a system administration topic. It treats an instance as the place where your code runs and asks what a developer must get right around it:

Typical question shapes: a scenario with one deciding constraint; an error message or metric to diagnose; "Which TWO actions…" with five options; the exact API action, CLI command, header or setting that does the job.

Domain map: Troubleshooting and Optimization → §4, §5 (cost), §8, §10, §11 · Security → §1, §2, §6 (encryption), §9 (TLS) · Development with AWS Services → §1, §3, §7 · Deployment → §3, §6 (AMIs), §10.

Out of scope: OS administration depth (kernel, package, file-system or Windows administration beyond the commands shown here) and network infrastructure design (subnet, routing, NAT, peering or Transit Gateway architecture). NACLs, route tables and VPC endpoints appear only as blockers you must recognise.

# Topic Why it is on the exam
1 IAM roles, instance profiles and the credential chain The secure way for code on EC2 to call AWS, and why a stray key on the instance silently wins
2 Instance metadata service (IMDSv2) Where role credentials come from; enforcing IMDSv2 and fixing containers that lose credentials
3 User data and bootstrapping Turning a generic AMI into your app server: run-once behaviour, base64, logs, no secrets
4 Security groups and connectivity troubleshooting Timeout vs refused vs AccessDenied decides the fix; shell access without open ports
5 Purchasing options and Spot The cheapest option that respects interruption and commitment constraints; handling Spot interruptions in code
6 EBS volumes, snapshots, encryption and AMIs Volume type under an IOPS or cost constraint; encrypt, copy and share workflows; KMS launch failures
7 Choosing storage: EBS vs EFS vs instance store Persistence and sharing decide the answer; EFS mounts, modes and permissions
8 Elastic Load Balancing: ALB, NLB and GWLB Pick the load balancer by layer and feature; decode 502/503/504 and client-IP headers
9 Sticky sessions, TLS certificates and SNI Session affinity vs stateless design; ACM certificates, SNI and TLS policies on listeners
10 Auto Scaling: policies, health checks and instance refresh The right scaling policy and health check settings, safe rollouts, lifecycle hooks
11 CloudWatch for EC2: metrics, agent and status checks What EC2 measures by itself vs what needs the agent; alarms, high-resolution metrics, recovery

1. IAM roles, instance profiles and the credential chain

Concept

Exam facts

Developer angle

Create the role and profile, then launch or attach:

aws iam create-role --role-name OrdersAppRole --assume-role-policy-document file://trust.json
aws iam put-role-policy --role-name OrdersAppRole --policy-name orders-s3 --policy-document file://perms.json
aws iam create-instance-profile --instance-profile-name OrdersAppProfile
aws iam add-role-to-instance-profile --instance-profile-name OrdersAppProfile --role-name OrdersAppRole

# at launch: pass the PROFILE, not the role
aws ec2 run-instances --image-id ami-0abcdef1234567890 --instance-type t3.micro \
  --iam-instance-profile Name=OrdersAppProfile

# on a running instance (no reboot): attach if none, otherwise replace
aws ec2 associate-iam-instance-profile --instance-id i-0123456789abcdef0 \
  --iam-instance-profile Name=OrdersAppProfile
aws ec2 describe-iam-instance-profile-associations \
  --filters Name=instance-id,Values=i-0123456789abcdef0
aws ec2 replace-iam-instance-profile-association \
  --association-id iip-assoc-0044d817db6c0a4ba --iam-instance-profile Name=OrdersAppProfileV2

trust.json (EC2 may assume the role) and perms.json (resource-scoped permissions):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:PutObject"],
    "Resource": "arn:aws:s3:::orders-bucket/incoming/*"
  }]
}

Policy for the person or pipeline that launches instances with this role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ec2:RunInstances", "ec2:AssociateIamInstanceProfile",
                 "ec2:ReplaceIamInstanceProfileAssociation",
                 "ec2:DescribeIamInstanceProfileAssociations"],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam::111122223333:role/OrdersAppRole",
      "Condition": { "StringEquals": { "iam:PassedToService": "ec2.amazonaws.com" } }
    }
  ]
}

Diagnose "wrong identity / AccessDenied" on the instance:

aws sts get-caller-identity             # expect ...:assumed-role/OrdersAppRole/i-0123456789abcdef0
env | grep '^AWS_'                      # stray AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_PROFILE?
ls -la ~/.aws /root/.aws 2>/dev/null    # stray credentials or config files?
aws configure list                      # shows the source (type/location) of each value in use
aws sts decode-authorization-message --encoded-message "$ENCODED_MSG" \
  --query DecodedMessage --output text

Traps

Practice questions

  1. A developer deploys an inventory service to an EC2 instance launched from a shared team AMI. The instance profile's role allows only dynamodb:Query on one table, yet the service's calls fail with AccessDenied errors that name the IAM user ci-deployer as the caller. What is the MOST likely cause?

    • A. The role's trust policy does not allow ec2.amazonaws.com to assume it.
    • B. A credentials file left in the AMI is found before the instance profile in the chain.
    • C. IMDSv2 is required on the instance and the SDK cannot obtain a session token.
    • D. The role's maximum session duration expired the instance credentials after one hour.
  2. A developer must give a running production instance the permissions of a new role, ReportsRoleV2, without stopping it. The instance already has the instance profile ReportsProfile (containing ReportsRole) attached, and a profile ReportsProfileV2 containing the new role exists. Which TWO actions are required? (Select TWO.)

    • A. Call replace-iam-instance-profile-association with the current association ID and ReportsProfileV2.
    • B. Stop the instance, call associate-iam-instance-profile, and then start it again.
    • C. Make sure the caller has iam:PassRole permission on the ReportsRoleV2 role ARN.
    • D. Add ReportsRoleV2 to ReportsProfile so that the profile holds both roles.
    • E. Reboot the instance so the SDK reloads credentials from the metadata service.
Answers
  1. B. The error names an IAM user, so long-term keys from a higher-priority provider (~/.aws/credentials baked into the AMI) were used. A or C would leave the SDK with no role credentials at all, not an IAM user's; D is wrong because EC2 role credentials ignore MaxSessionDuration and rotate automatically.
  2. A, C. Replacing the association works live; the caller needs iam:PassRole on the new role. B causes downtime, and associate fails while a profile is attached; D fails because a profile holds only one role; E is unnecessary because SDKs refresh credentials automatically.

2. Instance metadata service (IMDSv2)

Concept

Exam facts

Developer angle

Token flow with curl (-f makes curl fail on HTTP errors instead of storing the error text in TOKEN):

TOKEN=$(curl -sf -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id
curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region
curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/dynamic/instance-identity/document

# IMDSv1-style call (no token): prints 401 when HttpTokens=required
curl -s -o /dev/null -w "%{http_code}\n" http://169.254.169.254/latest/meta-data/instance-id

Audit, then enforce:

# instances that still allow IMDSv1
aws ec2 describe-instances --filters "Name=metadata-options.http-tokens,Values=optional" \
  --query "Reservations[*].Instances[*].InstanceId" --output text

# IMDSv1 calls over the last week (want Sum = 0 before switching)
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name MetadataNoToken \
  --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
  --start-time 2026-10-05T00:00:00Z --end-time 2026-10-12T00:00:00Z \
  --period 86400 --statistics Sum

# running instance: require v2 now (no restart); hop limit 2 for containers
aws ec2 modify-instance-metadata-options --instance-id i-0123456789abcdef0 \
  --http-tokens required --http-endpoint enabled --http-put-response-hop-limit 2
aws ec2 describe-instances --instance-ids i-0123456789abcdef0 \
  --query "Reservations[].Instances[].MetadataOptions"

# new instance
aws ec2 run-instances --image-id ami-0abcdef1234567890 --instance-type t3.micro \
  --metadata-options "HttpEndpoint=enabled,HttpTokens=required,HttpPutResponseHopLimit=2"

# account default for new launches in one Region, then hard enforcement
aws ec2 modify-instance-metadata-defaults --region eu-west-1 \
  --http-tokens required --http-put-response-hop-limit 2
aws ec2 modify-instance-metadata-defaults --region eu-west-1 --http-tokens-enforced enabled
aws ec2 get-instance-metadata-defaults --region eu-west-1

# AMI level (irreversible)
aws ec2 modify-image-attribute --image-id ami-0abcdef1234567890 --imds-support v2.0

Launch template data (aws ec2 create-launch-template-version … --launch-template-data file://lt.json):

{
  "MetadataOptions": {
    "HttpEndpoint": "enabled",
    "HttpTokens": "required",
    "HttpPutResponseHopLimit": 2
  }
}

Guardrail policy (identity policy or SCP):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireImdsV2AtLaunch",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": { "StringNotEquals": { "ec2:MetadataHttpTokens": "required" } }
    },
    {
      "Sid": "CapHopLimit",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": { "NumericGreaterThan": { "ec2:MetadataHttpPutResponseHopLimit": "3" } }
    },
    {
      "Sid": "RejectCredentialsFromImdsV1",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": { "NumericLessThan": { "ec2:RoleDelivery": "2.0" } }
    }
  ]
}

Traps

Practice questions

  1. A company runs Docker containers with bridge networking on EC2 instances. After the security team set HttpTokens=required, processes on the host still call AWS successfully, but SDK calls from inside the containers can no longer find credentials. IMDSv2 must stay enforced. What should a developer do?

    • A. Set the instance's HTTP PUT response hop limit to 2.
    • B. Set HttpTokens back to optional so the SDK can fall back.
    • C. Enable the IMDS IPv6 endpoint on the instances.
    • D. Request tokens with a TTL of 21,600 seconds in the containers.
  2. A security team must eliminate IMDSv1 in an AWS account without breaking running applications, and future launches in each Region must default to IMDSv2. Which TWO actions should a developer take? (Select TWO.)

    • A. Confirm that each instance's MetadataNoToken metric is zero, then set HttpTokens to required on it.
    • B. Run modify-instance-metadata-defaults with --http-tokens required in every Region in use.
    • C. Set HttpEndpoint to disabled on every instance that still accepts IMDSv1 requests.
    • D. Stop and start each instance after changing its metadata options so the change applies.
    • E. Rely on the new account default to convert existing instances at their next reboot.
Answers
  1. A. The container is one extra network hop, so the token response (IP TTL 1) expires before reaching it; a hop limit of 2 fixes this and keeps IMDSv2. B weakens security and breaks the constraint; C adds no hops; D changes the session length, not the hop count.
  2. A, B. Check for zero IMDSv1 traffic before requiring v2 on each instance, and set the per-Region account default for new launches. C removes role credentials; D is unnecessary because changes apply immediately; E is wrong because account defaults affect only new launches.

3. User data and bootstrapping

Concept

Exam facts

Developer angle

Bootstrap script for Amazon Linux 2023 that pulls a secret with the instance role instead of embedding it:

#!/bin/bash
set -euxo pipefail        # stop on errors; trace goes to /var/log/cloud-init-output.log
dnf install -y nginx
TOKEN=$(curl -sf -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
REGION=$(curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/placement/region)
mkdir -p /etc/orders
# role needs secretsmanager:GetSecretValue on this secret (+ kms:Decrypt for a customer managed key)
aws secretsmanager get-secret-value --secret-id prod/orders/db --region "$REGION" \
  --query SecretString --output text > /etc/orders/db.json
chmod 600 /etc/orders/db.json
# Parameter Store alternative:
# aws ssm get-parameter --name /prod/orders/db-password --with-decryption \
#   --query Parameter.Value --output text --region "$REGION"
systemctl enable --now nginx

Same idea as cloud-config:

#cloud-config
package_update: true
packages:
  - nginx
runcmd:
  - systemctl enable --now nginx

Make something run on every later boot (written during the first boot):

#!/bin/bash
cat > /var/lib/cloud/scripts/per-boot/refresh-config.sh <<'EOF'
#!/bin/bash
/opt/orders/refresh-config.sh
EOF
chmod +x /var/lib/cloud/scripts/per-boot/refresh-config.sh

Pass, change and read user data with the CLI:

# launch: the CLI base64-encodes the file for run-instances
aws ec2 run-instances --image-id ami-0abcdef1234567890 --instance-type t3.micro \
  --iam-instance-profile Name=OrdersAppProfile --user-data file://bootstrap.sh

# change: stop first, encode yourself, start again (the new script will NOT run by itself)
aws ec2 stop-instances --instance-ids i-0123456789abcdef0
aws ec2 wait instance-stopped --instance-ids i-0123456789abcdef0
base64 bootstrap.sh > bootstrap.b64
aws ec2 modify-instance-attribute --instance-id i-0123456789abcdef0 \
  --attribute userData --value file://bootstrap.b64
aws ec2 start-instances --instance-ids i-0123456789abcdef0

# read back: the API returns base64
aws ec2 describe-instance-attribute --instance-id i-0123456789abcdef0 --attribute userData \
  --output text --query "UserData.Value" | base64 --decode

# launch template version: UserData must already be base64 (GNU coreutils base64 shown)
aws ec2 create-launch-template-version --launch-template-id lt-0abcd290751193123 \
  --source-version 1 --launch-template-data "{\"UserData\":\"$(base64 -w0 bootstrap.sh)\"}"

boto3 encodes UserData for run_instances (but not for launch templates):

import boto3

ec2 = boto3.client("ec2")
with open("bootstrap.sh") as f:
    ec2.run_instances(ImageId="ami-0abcdef1234567890", InstanceType="t3.micro",
                      MinCount=1, MaxCount=1,
                      IamInstanceProfile={"Name": "OrdersAppProfile"},
                      UserData=f.read())   # plain text: boto3 base64-encodes it

Debug a failed bootstrap:

sudo tail -n 50 /var/log/cloud-init-output.log
cloud-init status --long
aws ec2 get-console-output --instance-id i-0123456789abcdef0 --output text   # from your workstation

CloudFormation pointer (wait for the app before the stack continues):

WebServer:
  Type: AWS::EC2::Instance
  CreationPolicy:
    ResourceSignal:
      Count: 1
      Timeout: PT15M
  Metadata:
    AWS::CloudFormation::Init:
      config:
        commands:
          01_install:
            command: dnf install -y nginx && systemctl enable --now nginx
  Properties:
    ImageId: ami-0abcdef1234567890
    InstanceType: t3.micro
    IamInstanceProfile: !Ref OrdersInstanceProfile
    UserData:
      Fn::Base64: !Sub |
        #!/bin/bash
        dnf install -y aws-cfn-bootstrap
        /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServer --region ${AWS::Region}
        /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServer --region ${AWS::Region}

Traps

Practice questions

  1. A developer's launch template passes a database password to EC2 instances in user data so that a startup script can write the application's configuration file. A security review flags this design. Which approach is MOST secure while keeping the bootstrap fully automated?

    • A. Base64-encode the password before adding it to the user data script.
    • B. Keep it in Secrets Manager; retrieve it at boot with the instance role.
    • C. Store the password in an instance tag and read it from instance metadata tags.
    • D. Bake the password into a configuration file inside a private golden AMI.
  2. An Auto Scaling group's new instances take about 12 minutes to pass health checks because user data compiles and installs dependencies at boot. The team wants faster, repeatable scale-out with the least ongoing manual work. Which TWO actions should a developer take? (Select TWO.)

    • A. Build an AMI with the dependencies preinstalled using an EC2 Image Builder pipeline.
    • B. Keep only short, environment-specific steps, such as fetching config, in user data.
    • C. Move the install commands into a #cloud-boothook so that they run earlier in boot.
    • D. Raise the group's default cooldown so instances have more time to finish booting.
    • E. Update the user data on running instances so that new instances inherit the change.
Answers
  1. B. The secret stays encrypted in Secrets Manager and is fetched at boot with temporary role credentials. A is only an encoding, and user data is readable anyway; C exposes the value to anyone who can read tags or IMDS; D spreads a long-lived secret to every copy of the AMI and makes rotation hard.
  2. A, B. A pre-baked AMI from an automated Image Builder pipeline removes the slow installs from boot, and user data shrinks to fast per-environment steps. C still installs at boot (and on every boot); D only delays further scaling activity; E is impossible while instances run and does not change what the launch template gives new instances.

4. Security groups and connectivity troubleshooting

Concept

Exam facts

Classic ports (all TCP)

Port Service Typical exam use
22 SSH, SFTP Linux shell, EC2 Instance Connect
21 FTP Legacy, unencrypted file transfer
80 / 443 HTTP / HTTPS Web and ALB listeners; 443 is also how SSM Agent and SDKs reach AWS APIs
3389 RDP Windows desktop
3306 MySQL, MariaDB, Aurora MySQL DB SG allows it from the app SG
5432 PostgreSQL, Aurora PostgreSQL DB SG allows it from the app SG
1433 Microsoft SQL Server DB SG allows it from the app SG
1521 Oracle DB SG allows it from the app SG
6379 Redis OSS / Valkey (ElastiCache) Cache SG allows it from the app SG
11211 Memcached (ElastiCache) Cache SG allows it from the app SG
2049 NFS (Amazon EFS) Mount-target SG allows it from the instance SG

Error-symptom matrix

What you see What it proves Usual cause Fix
Connection timed out / request hangs Nothing on the path answered SG lacks an inbound rule for that port and source; NACL deny or missing ephemeral return rule; no route to an IGW/NAT; no public IP; corporate firewall Fix SG/NACL/route, add a public IP or EIP, or avoid inbound access with Session Manager / EIC Endpoint
Connection refused (instant) Packet reached the OS, nobody listening Service stopped or crashed, wrong port, app bound to 127.0.0.1, host firewall rejecting Start or fix the process, listen on 0.0.0.0 and the right port
Permission denied (publickey) TCP to port 22 worked; SSH authentication failed Wrong user (ec2-user, ubuntu, admin...), wrong private key, key file readable by others (UNPROTECTED PRIVATE KEY FILE), no key pair at launch Correct user/key, chmod 400 the key, or use EC2 Instance Connect / Session Manager
HTTP 403 Forbidden from your site or ALB Network fine; an HTTP layer refused Application authorization, AWS WAF rule on the ALB, S3/CloudFront access policy Fix the app, WAF rule or resource policy, not the SG
AccessDenied / Client.UnauthorizedOperation (HTTP 403) from an AWS API call The call reached AWS; IAM said no Role lacks the action, explicit deny (SCP, permissions boundary, resource policy), unexpected identity in the credential chain aws sts get-caller-identity, aws sts decode-authorization-message, fix the policy (§1)
Unable to locate credentials The SDK/CLI found no credential source No instance profile attached, IMDS disabled or unreachable, IMDSv2 hop limit 1 inside a container Attach a role, fix the metadata options (§1, §2)
Connect timeout on endpoint URL (AWS CLI/boto3 wording; hangs first) Packets to the AWS API endpoint were dropped Private subnet without NAT gateway or VPC endpoint; SG or NACL egress blocks 443; interface endpoint's SG lacks inbound 443 from the caller; interface endpoint without private DNS, so the default name still resolves to public IPs Add a NAT gateway or the service's VPC endpoint, fix the SG/NACL rules, or enable private DNS on the endpoint
Could not connect to the endpoint URL (AWS CLI/boto3 wording; usually fast) Name resolution or connection setup failed Misspelled Region or --endpoint-url, service not offered in that Region, VPC DNS resolution off or custom DNS servers that cannot resolve AWS names, local endpoint (for example DynamoDB Local) not running Fix the Region or endpoint, the VPC DNS settings or DNS forwarding, or start the local endpoint

Developer angle

Least-privilege tiers and path checks (IDs are examples):

ALB_SG=sg-0a1b2c3d4e5f6a7b8; APP_SG=sg-0b2c3d4e5f6a7b8c9; DB_SG=sg-0c3d4e5f6a7b8c9d0
aws ec2 authorize-security-group-ingress --group-id "$ALB_SG" --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id "$APP_SG" --protocol tcp --port 8080 --source-group "$ALB_SG"
aws ec2 authorize-security-group-ingress --group-id "$DB_SG" --protocol tcp --port 5432 --source-group "$APP_SG"

# What is really attached to the instance?
aws ec2 describe-instances --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[].Instances[].{SGs:SecurityGroups[].GroupId,PublicIp:PublicIpAddress,Subnet:SubnetId}'

# Reachability Analyzer names the blocking SG, NACL or route
aws ec2 create-network-insights-path --source igw-0123456789abcdef0 \
  --destination i-0123456789abcdef0 --protocol tcp --destination-port 443
aws ec2 start-network-insights-analysis --network-insights-path-id nip-0123456789abcdef0

Shell access without opening port 22 to the world:

# Instance side: the role needs this managed policy (or enable Default Host Management Configuration)
aws iam attach-role-policy --role-name app-instance-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

# Your side: AWS CLI + Session Manager plugin + IAM permission ssm:StartSession
aws ssm start-session --target i-0123456789abcdef0

# Tunnel to a private database through the instance (no SSH, no bastion)
aws ssm start-session --target i-0123456789abcdef0 \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"host":["orders.cluster-abc123.eu-west-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["5432"]}'

# EC2 Instance Connect: push a 60-second key, then SSH within that window (port 22 must be allowed)
aws ec2-instance-connect send-ssh-public-key --instance-id i-0123456789abcdef0 \
  --instance-os-user ec2-user --ssh-public-key file://~/.ssh/id_rsa.pub
ssh -i ~/.ssh/id_rsa ec2-user@ec2-203-0-113-25.eu-west-1.compute.amazonaws.com

# Private instance through an EIC Endpoint (AWS CLI v2)
aws ec2 create-instance-connect-endpoint --subnet-id subnet-0123456789abcdef0 \
  --security-group-ids sg-0d4e5f6a7b8c9d0e1
aws ec2-instance-connect ssh --instance-id i-0123456789abcdef0 --os-user ec2-user --connection-type eice

Key and address checks (identity checks with sts get-caller-identity and sts decode-authorization-message: see §1):

chmod 400 my-key.pem                  # fixes "UNPROTECTED PRIVATE KEY FILE"
aws ec2 allocate-address --domain vpc # stable public IPv4 (billed hourly)
aws ec2 associate-address --instance-id i-0123456789abcdef0 --allocation-id eipalloc-0123456789abcdef0

Traps

Practice questions

  1. A developer runs an API on port 8080 on an EC2 instance. Calls from another instance in the same VPC fail immediately with Connection refused. The API instance's security group allows TCP 8080 from the caller's security group. What is the MOST likely cause?

    • A. The subnet's network ACL denies inbound traffic on port 8080.
    • B. The API process is not listening on port 8080 on the instance's network interface.
    • C. The instance role does not allow the ec2:AuthorizeSecurityGroupIngress action.
    • D. The API instance's security group has no outbound rule for the response traffic.
  2. A company runs EC2 instances in private subnets that have no NAT gateway and no internet gateway. Engineers need interactive shell access with no inbound ports open and with session API calls recorded in AWS CloudTrail. Which TWO steps will meet these requirements? (Select TWO.)

    • A. Attach an instance profile whose role includes the AmazonSSMManagedInstanceCore managed policy.
    • B. Create interface VPC endpoints for ssm, ssmmessages and ec2messages, with a security group that allows inbound HTTPS from the instances.
    • C. Add an inbound rule for TCP 22 from 0.0.0.0/0 to the instances' security group.
    • D. Allocate and associate an Elastic IP address with each instance.
    • E. Allow TCP 443 inbound on the instances' security group from the EC2 Instance Connect prefix list.
Answers
  1. B. An instant refusal is a TCP reset from the host: the packet arrived but no process accepted it (stopped service, wrong port or bound to 127.0.0.1). A NACL drop (A) would time out; C is an IAM permission unrelated to traffic; D is wrong because SGs are stateful.
  2. A, B. Session Manager needs agent permissions (the managed policy) and an outbound path to the SSM endpoints, which here can only be interface endpoints. C opens SSH to everyone and still has no route; D gives no route without an internet gateway; E is the wrong port and EC2 Instance Connect is SSH-based anyway.

5. Purchasing options and Spot

Concept

Exam facts

Developer angle

Watch for the 2-minute notice from inside a Spot instance (IMDSv2):

#!/bin/bash
while true; do
  TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
    -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
  CODE=$(curl -s -o /dev/null -w '%{http_code}' -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/meta-data/spot/instance-action)
  if [ "$CODE" = "200" ]; then
    /opt/worker/bin/checkpoint-to-s3   # persist progress, stop pulling new jobs
    break
  fi
  sleep 5
done

React centrally with EventBridge (the Lambda function also needs a resource policy that allows events.amazonaws.com to invoke it):

aws events put-rule --name spot-warnings --event-pattern \
  '{"source":["aws.ec2"],"detail-type":["EC2 Spot Instance Interruption Warning","EC2 Instance Rebalance Recommendation"]}'
aws events put-targets --rule spot-warnings \
  --targets Id=drain,Arn=arn:aws:lambda:eu-west-1:111122223333:function:drain-worker

ASG with an On-Demand base and Spot for the rest (aws autoscaling create-auto-scaling-group --cli-input-json file://ci-runners.json):

{
  "AutoScalingGroupName": "ci-runners",
  "MinSize": 2,
  "MaxSize": 40,
  "DesiredCapacity": 6,
  "VPCZoneIdentifier": "subnet-0a1b2c3d4e5f6a7b8,subnet-0b2c3d4e5f6a7b8c9",
  "CapacityRebalance": true,
  "MixedInstancesPolicy": {
    "LaunchTemplate": {
      "LaunchTemplateSpecification": { "LaunchTemplateName": "ci-runner", "Version": "$Latest" },
      "Overrides": [
        { "InstanceType": "m7i.large" },
        { "InstanceType": "m6i.large" },
        { "InstanceType": "m7a.large" },
        { "InstanceType": "c7i.xlarge" }
      ]
    },
    "InstancesDistribution": {
      "OnDemandBaseCapacity": 2,
      "OnDemandPercentageAboveBaseCapacity": 20,
      "SpotAllocationStrategy": "price-capacity-optimized"
    }
  }
}

Here 2 instances are always On-Demand; everything above them is 20% On-Demand and 80% Spot.

Single-type Spot launch and clean shutdown of a persistent request:

aws ec2 run-instances --launch-template LaunchTemplateName=batch-worker,Version=3 \
  --instance-market-options '{"MarketType":"spot","SpotOptions":{"SpotInstanceType":"one-time","InstanceInterruptionBehavior":"terminate"}}'

aws ec2 describe-spot-instance-requests --spot-instance-request-ids sir-08b93456 \
  --query 'SpotInstanceRequests[].{Type:Type,State:State,Instance:InstanceId}'
aws ec2 cancel-spot-instance-requests --spot-instance-request-ids sir-08b93456   # 1. stop relaunches
aws ec2 terminate-instances --instance-ids i-0123456789abcdef0                   # 2. then terminate

Traps

Practice questions

  1. A company runs a steady 24/7 workload on m6i instances in eu-west-1. Over the next three years it plans to move part of the workload to Graviton-based m7g instances and some services to AWS Fargate. Which purchase gives the largest discount that keeps applying automatically after these changes?

    • A. An EC2 Instance Savings Plan for the m6i family in eu-west-1
    • B. A Compute Savings Plan
    • C. Standard Reserved Instances for m6i in eu-west-1
    • D. On-Demand Capacity Reservations for m6i in eu-west-1
  2. A developer's job-processing application runs on Spot Instances and must save its progress to Amazon S3 before an instance is reclaimed. Which TWO mechanisms give the application advance warning that it can act on? (Select TWO.)

    • A. Poll http://169.254.169.254/latest/meta-data/spot/instance-action with an IMDSv2 token every few seconds.
    • B. Create an Amazon EventBridge rule that matches the EC2 Spot Instance Interruption Warning event and invokes a draining function.
    • C. Create a CloudWatch alarm on the StatusCheckFailed_System metric of each instance.
    • D. Enable termination protection on the Spot Instances.
    • E. Set the Spot maximum price equal to the On-Demand price.
Answers
  1. B. Only a Compute Savings Plan applies across instance families (m6i to m7g) and to Fargate, at up to 66% off. A and C are locked to the m6i family (and do not cover Fargate); D reserves capacity at the On-Demand rate with no discount.
  2. A, B. The spot/instance-action metadata item and the EventBridge interruption warning both appear about 2 minutes before a stop or terminate. C reports host failures, not reclaims; D cannot be enabled on Spot Instances and gives no warning anyway; E is already the default and adds no warning.

6. EBS volumes, snapshots, encryption and AMIs

Concept

Exam facts

Volumes and attachment

Volume types (current limits)

Elastic Volumes (change a volume in place)

Encryption

Snapshots

AMIs

Developer angle

Encrypt an existing unencrypted volume (the classic snapshot path):

# 1) snapshot the unencrypted volume
SNAP=$(aws ec2 create-snapshot --volume-id vol-01234567890abcdef \
  --description "pre-encryption" --query SnapshotId --output text)
aws ec2 wait snapshot-completed --snapshot-ids "$SNAP"

# 2) restore it as an encrypted volume in the SAME AZ as the instance
NEW=$(aws ec2 create-volume --snapshot-id "$SNAP" --availability-zone us-east-1a \
  --volume-type gp3 --encrypted --kms-key-id alias/app-ebs \
  --query VolumeId --output text)
aws ec2 wait volume-available --volume-ids "$NEW"

# 3) swap it in (for a root volume: stop first and reuse the RootDeviceName)
aws ec2 describe-instances --instance-ids i-1234567890abcdef0 \
  --query "Reservations[].Instances[].RootDeviceName" --output text
aws ec2 stop-instances --instance-ids i-1234567890abcdef0
aws ec2 wait instance-stopped --instance-ids i-1234567890abcdef0
aws ec2 detach-volume --volume-id vol-01234567890abcdef
aws ec2 wait volume-available --volume-ids vol-01234567890abcdef
aws ec2 attach-volume --volume-id "$NEW" --instance-id i-1234567890abcdef0 --device /dev/xvda
aws ec2 start-instances --instance-ids i-1234567890abcdef0

Newer shortcuts. Both paths are valid today. The snapshot path is the classic answer, and questions written before October 2025 offer only that path; choose copy-volumes when it is listed and the copy stays in the same AZ.

# same-AZ encrypted copy: name the type (and match the source's performance), or the copy is gp2
aws ec2 copy-volumes --source-volume-id vol-01234567890abcdef \
  --volume-type gp3 --iops 6000 --throughput 250 \
  --encrypted --kms-key-id alias/app-ebs

# swap a prepared, detached volume from the same AZ in as the root:
# automatic reboot, same instance ID and IPs (add --delete-replaced-root-volume to drop the old root)
aws ec2 create-replace-root-volume-task --instance-id i-1234567890abcdef0 \
  --volume-id vol-0fedcba9876543210
aws ec2 describe-replace-root-volume-tasks \
  --filters Name=instance-id,Values=i-1234567890abcdef0 --query "ReplaceRootVolumeTasks[].TaskState"

# the old root's DeleteOnTermination does not carry over: set it on the new root
aws ec2 modify-instance-attribute --instance-id i-1234567890abcdef0 \
  --block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"DeleteOnTermination":true}}]'

⚠️ Changed: create-replace-root-volume-task --volume-id swaps in an existing volume that is available (attached to nothing) and in the instance's AZ (since 2026-07-09). Older material may say root replacement can only restore the launch state, a snapshot of the root or an AMI. You can go from an unencrypted root to an encrypted one, but EC2 rejects an encrypted root → unencrypted volume swap.

Share an encrypted golden AMI from account 111122223333 with account 444455556666. Statements to add to the customer managed key's policy (keep the existing statements). The conditioned grant statement covers direct run-instances launches and copy-image, where EC2 creates the grant on the caller's behalf. Add the last statement only if an Auto Scaling group in the target account launches the shared AMI directly, because that account must call CreateGrant itself for its service-linked role:

{
  "Statement": [
    {
      "Sid": "AllowUseOfTheKeyByTargetAccount",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::444455556666:root"},
      "Action": ["kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey"],
      "Resource": "*"
    },
    {
      "Sid": "AllowGrantsForDirectLaunchesAndCopies",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::444455556666:root"},
      "Action": ["kms:CreateGrant", "kms:ListGrants", "kms:RevokeGrant"],
      "Resource": "*",
      "Condition": {"Bool": {"kms:GrantIsForAWSResource": true}}
    },
    {
      "Sid": "AllowTargetToGrantItsAutoScalingRole",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::444455556666:root"},
      "Action": "kms:CreateGrant",
      "Resource": "*"
    }
  ]
}
# owner: an aws/ebs-encrypted AMI must first be re-keyed with a customer managed key
SHARED=$(aws ec2 copy-image --region us-east-1 --source-region us-east-1 \
  --source-image-id ami-0abcdef1234567890 --name app-v42-cmk --encrypted \
  --kms-key-id arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab \
  --query ImageId --output text)
aws ec2 wait image-available --image-ids "$SHARED"

# owner: launch permission (plus snapshot read so the target can also copy the AMI)
aws ec2 modify-image-attribute --image-id "$SHARED" \
  --launch-permission "Add=[{UserId=444455556666}]"
for s in $(aws ec2 describe-images --image-ids "$SHARED" \
    --query "Images[].BlockDeviceMappings[].Ebs.SnapshotId" --output text); do
  aws ec2 modify-snapshot-attribute --snapshot-id "$s" \
    --attribute createVolumePermission --operation-type add --user-ids 444455556666
done

# target (needs an IAM policy allowing the same kms:* actions on the owner's key ARN):
# take ownership by copying under its own key
aws ec2 copy-image --region us-east-1 --source-region us-east-1 \
  --source-image-id "$SHARED" --name app-v42-local --encrypted --kms-key-id alias/team-ebs

# target, only if its Auto Scaling group launches the shared AMI directly (no local copy):
# grant the owner's key to its own service-linked role (IAM must allow kms:CreateGrant on that key ARN)
aws kms create-grant --region us-east-1 \
  --key-id arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab \
  --grantee-principal arn:aws:iam::444455556666:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling \
  --operations Encrypt Decrypt ReEncryptFrom ReEncryptTo GenerateDataKey GenerateDataKeyWithoutPlaintext DescribeKey CreateGrant

Cross-Region copy for DR with boto3 (call the API in the destination Region):

import boto3

ec2_dr = boto3.client("ec2", region_name="eu-west-1")
resp = ec2_dr.copy_image(
    SourceRegion="us-east-1",
    SourceImageId="ami-0abcdef1234567890",
    Name="web-v42-dr",
    Encrypted=True,
    KmsKeyId="alias/dr-ebs",   # a key that exists in eu-west-1
    CopyImageTags=True,
)
ec2_dr.get_waiter("image_available").wait(ImageIds=[resp["ImageId"]])
print(resp["ImageId"])          # new ID: update the DR launch template with it

Keep the root volume when the instance is terminated, then verify:

aws ec2 modify-instance-attribute --instance-id i-1234567890abcdef0 \
  --block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"DeleteOnTermination":false}}]'
aws ec2 describe-instances --instance-ids i-1234567890abcdef0 \
  --query "Reservations[].Instances[].BlockDeviceMappings[].[DeviceName,Ebs.DeleteOnTermination]"

Grow and tune a gp3 volume live, then extend the file system:

aws ec2 modify-volume --volume-id vol-01234567890abcdef --size 200 --iops 12000 --throughput 500
aws ec2 describe-volumes-modifications --volume-ids vol-01234567890abcdef \
  --query "VolumesModifications[].[ModificationState,Progress]"
# on the instance (Nitro, XFS root partition 1)
sudo growpart /dev/nvme0n1 1 && sudo xfs_growfs -d /

Always launch the latest AL2023 AMI:

aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
  --query Parameter.Value --output text
aws ec2 run-instances --instance-type t3.micro \
  --image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Parameters:
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Resources:
  AppInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.micro
      ImageId: !Ref LatestAmiId

Protection and cost controls:

aws ec2 enable-ebs-encryption-by-default --region eu-west-1
aws ec2 modify-ebs-default-kms-key-id --kms-key-id alias/app-ebs --region eu-west-1
aws rbin create-rule --resource-type EBS_SNAPSHOT --description "prod snapshots 14 days" \
  --retention-period RetentionPeriodValue=14,RetentionPeriodUnit=DAYS \
  --resource-tags ResourceTagKey=env,ResourceTagValue=prod
aws ec2 modify-snapshot-tier --snapshot-id snap-0abcdef1234567890 --storage-tier archive
aws ec2 restore-snapshot-tier --snapshot-id snap-0abcdef1234567890 --temporary-restore-days 5
aws ec2 enable-fast-snapshot-restores --availability-zones us-east-1a us-east-1b \
  --source-snapshot-ids snap-0abcdef1234567890
aws ec2 deregister-image --image-id ami-0abcdef1234567890 --delete-associated-snapshots

Traps

Practice questions

  1. A developer needs one 500 GiB EBS data volume for a self-managed database on a Nitro-based instance. The volume must sustain 40,000 IOPS and 1,000 MiB/s. Sub-millisecond latency and Multi-Attach are not required. Which volume configuration meets the requirements at the LOWEST cost?

    • A. A gp2 volume enlarged until its baseline reaches 40,000 IOPS
    • B. A gp3 volume provisioned with 40,000 IOPS and 1,000 MiB/s
    • C. An io2 Block Express volume provisioned with 40,000 IOPS
    • D. An st1 volume sized for 1,000 MiB/s of throughput
  2. Account A wants an Auto Scaling group in account B to launch instances from a golden AMI. The AMI's snapshots are encrypted with the AWS managed key aws/ebs. Which TWO actions are required? (Select TWO.)

    • A. In account A, copy the AMI and encrypt the copy with a customer managed KMS key.
    • B. Add account B to the key policy of the aws/ebs key in account A.
    • C. Allow account B to use the customer managed key and add a launch permission for account B on the new AMI.
    • D. Make the AMI public so that account B can launch it.
    • E. Turn on EBS encryption by default in account B.
Answers
  1. B. gp3 allows up to 500 IOPS per GiB (250,000 for 500 GiB, capped at 80,000) and 1,000 MiB/s needs only 4,000 IOPS, at the lowest SSD price. A fails: gp2 tops out at 16,000 IOPS and 250 MiB/s. C works but costs more for a capability (sub-ms latency, 99.999%) that isn't required. D fails: st1 is limited to 500 IOPS.
  2. A, C. aws/ebs resources can't be shared, so re-encrypt the AMI's snapshots with a customer managed key, then share both the key (key policy, including an unconditioned kms:CreateGrant so that account B can create a grant for its AWSServiceRoleForAutoScaling) and the AMI's launch permission. B fails: AWS managed key policies can't be edited. D fails: encrypted snapshots can't be made public, and it would expose the image. E fails: it only encrypts new resources in account B and grants nothing on account A's key.

7. Choosing storage: EBS vs EFS vs instance store

Concept

Exam facts

Instance store

EBS (details in §6)

EFS

Other storage (awareness)

Decision table

Requirement Choose Why the others fail
Shared POSIX files for instances in several AZs (CMS, an uploads directory the application reads and writes as files, home directories) EFS Regional + a mount target per AZ EBS is single-AZ and single-writer; instance store is per instance and ephemeral
Highest IOPS for scratch/cache data that can be rebuilt Instance store EBS and EFS add network latency and cost
Persistent single-instance database or boot volume EBS gp3 (io2 Block Express beyond 80,000 IOPS) Instance store loses data on stop; EFS adds NFS latency
Shared block device for a cluster-aware application in one AZ io2 Multi-Attach (≤ 16 Nitro instances) EFS is file-level; gp3 can't Multi-Attach
Shared files for Lambda functions or containers EFS access point Lambda /tmp is per execution environment; EBS can't attach to Lambda
Windows SMB share with Active Directory FSx for Windows File Server EFS is Linux-only
HPC/ML parallel file system over S3 data FSx for Lustre EFS isn't built for that parallel throughput profile
Objects (including user uploads the app handles through the SDK), static assets, backups, data lake S3 File and block storage cost more and don't scale the same way

Developer angle

Create an EFS file system that follows current guidance, add a mount target per AZ and open NFS only from the app tier:

FS=$(aws efs create-file-system --creation-token web-shared-01 \
  --performance-mode generalPurpose --throughput-mode elastic --encrypted \
  --tags Key=Name,Value=web-shared --query FileSystemId --output text)
# wait until LifeCycleState is "available"
aws efs describe-file-systems --file-system-id "$FS" --query "FileSystems[0].LifeCycleState"

# one mount target per AZ (repeat with a subnet in each AZ)
aws efs create-mount-target --file-system-id "$FS" \
  --subnet-id subnet-0123456789abcdef0 --security-groups sg-0123456789abcdef0

# mount-target SG: allow TCP 2049 only from the web servers' SG
aws ec2 authorize-security-group-ingress --group-id sg-0123456789abcdef0 \
  --protocol tcp --port 2049 --source-group sg-0fedcba9876543210

# lifecycle: IA after 30 days, Archive after 90, back to Standard on first access
aws efs put-lifecycle-configuration --file-system-id "$FS" \
  --lifecycle-policies '[{"TransitionToIA":"AFTER_30_DAYS"},{"TransitionToArchive":"AFTER_90_DAYS"},{"TransitionToPrimaryStorageClass":"AFTER_1_ACCESS"}]'

Mount with TLS and IAM authorization (for example from user data, §3), and persist it across reboots:

sudo dnf install -y amazon-efs-utils
sudo mkdir -p /mnt/shared
sudo mount -t efs -o tls,iam fs-0123456789abcdef0:/ /mnt/shared
echo 'fs-0123456789abcdef0:/ /mnt/shared efs _netdev,tls,iam 0 0' | sudo tee -a /etc/fstab

File system policy that rejects unencrypted connections and gives the app role read/write (aws efs put-file-system-policy --file-system-id fs-0123456789abcdef0 --policy file://fs-policy.json):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyUnencryptedTransport",
      "Effect": "Deny",
      "Principal": {"AWS": "*"},
      "Action": "elasticfilesystem:Client*",
      "Resource": "arn:aws:elasticfilesystem:us-east-1:111122223333:file-system/fs-0123456789abcdef0",
      "Condition": {"Bool": {"aws:SecureTransport": "false"}}
    },
    {
      "Sid": "AppRoleReadWrite",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111122223333:role/web-app-ec2-role"},
      "Action": ["elasticfilesystem:ClientMount", "elasticfilesystem:ClientWrite"],
      "Resource": "arn:aws:elasticfilesystem:us-east-1:111122223333:file-system/fs-0123456789abcdef0"
    }
  ]
}

Give a VPC-attached Lambda function a shared directory through an access point:

AP_ARN=$(aws efs create-access-point --file-system-id fs-0123456789abcdef0 \
  --posix-user Uid=1001,Gid=1001 \
  --root-directory 'Path=/thumbnails,CreationInfo={OwnerUid=1001,OwnerGid=1001,Permissions=750}' \
  --query AccessPointArn --output text)
aws lambda update-function-configuration --function-name thumbnailer \
  --file-system-configs Arn="$AP_ARN",LocalMountPath=/mnt/thumbnails

Use instance store at boot (it is blank after every stop/start, so this belongs in a per-boot step, §3):

lsblk                                   # find the unformatted NVMe device, e.g. nvme1n1
sudo mkfs -t xfs /dev/nvme1n1
sudo mkdir -p /scratch && sudo mount /dev/nvme1n1 /scratch

Traps

Practice questions

  1. A developer keeps a rebuildable cache on the NVMe instance store of an EBS-backed m6id.xlarge instance that was launched with hibernation enabled. During maintenance, which operation keeps the cached data on the instance store volumes?

    • A. Changing the instance type to m6id.2xlarge
    • B. Running aws ec2 reboot-instances for the instance
    • C. Hibernating the instance overnight and resuming it in the morning
    • D. Running sudo shutdown -h now inside the OS, then starting the instance again
  2. A team is creating an Amazon EFS file system for Linux web servers that run in three Availability Zones. Traffic is spiky and hard to forecast, and file operations must stay low latency. Which TWO choices follow current AWS guidance? (Select TWO.)

    • A. Create a mount target in each Availability Zone and allow inbound TCP 2049 from the web servers' security group.
    • B. Choose the Max I/O performance mode to get the highest throughput.
    • C. Choose the Elastic throughput mode.
    • D. Choose a One Zone file system so that the data is stored in all three Availability Zones.
    • E. Choose the Bursting throughput mode so that throughput grows with the amount of stored data.
Answers
  1. B. A reboot keeps the instance on its host, so instance store data survives. A fails: changing the type requires a stop, and the new type's volumes start blank. C fails: hibernation erases every instance store block, just like a stop. D fails: an OS shutdown stops the instance (the default shutdown behavior), which also erases the volumes.
  2. A, C. Clients reach EFS through a mount target in their own AZ over NFS port 2049, and Elastic throughput is AWS's recommended mode for spiky, unpredictable workloads. B fails: Max I/O adds latency, is previous-generation and isn't supported with Elastic throughput. D fails: One Zone keeps data in a single AZ. E fails: Bursting ties throughput to stored size and credits, which spiky traffic can exhaust.

8. Elastic Load Balancing: ALB, NLB and GWLB

Concept

Exam facts

Health checks (per target group)

Security groups

ALB (layer 7)

NLB (layer 4)

GWLB (layer 3)

CLB (legacy): HTTP, HTTPS, TCP and SSL listeners; instances registered directly (no target groups); one certificate per listener (no SNI, §9). Use ALB or NLB for new work.

Cross-zone load balancing

Deregistration delay (connection draining)

Error codes generated by the ALB (counted in HTTPCode_ELB_4XX_Count / HTTPCode_ELB_5XX_Count)

Monitoring and logs

Developer angle

Target group with an application health endpoint, then content-based rules on the HTTPS listener:

aws elbv2 create-target-group --name orders-tg --protocol HTTP --port 8080 \
  --vpc-id vpc-0abc1234def567890 --target-type instance \
  --health-check-path /health --health-check-interval-seconds 15 \
  --healthy-threshold-count 3 --unhealthy-threshold-count 2 --matcher HttpCode=200

# /orders/* -> orders-tg (priority 10 is evaluated before higher numbers)
aws elbv2 create-rule --listener-arn "$LISTENER_ARN" --priority 10 \
  --conditions Field=path-pattern,Values='/orders/*' \
  --actions Type=forward,TargetGroupArn="$ORDERS_TG_ARN"

# host + header condition, canary split 90/10 between two target groups
aws elbv2 create-rule --listener-arn "$LISTENER_ARN" --priority 20 \
  --conditions file://api-conditions.json --actions file://canary-forward.json

api-conditions.json and canary-forward.json:

[
  { "Field": "host-header", "HostHeaderConfig": { "Values": ["api.example.com"] } },
  { "Field": "http-header", "HttpHeaderConfig": { "HttpHeaderName": "X-Client", "Values": ["mobile*"] } }
]
[
  {
    "Type": "forward",
    "ForwardConfig": {
      "TargetGroups": [
        { "TargetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/api-blue/0123456789abcdef", "Weight": 90 },
        { "TargetGroupArn": "arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/api-green/fedcba9876543210", "Weight": 10 }
      ]
    }
  }
]

Lambda behind an ALB: the CLI does not add the invoke permission for you (the console does).

TG_ARN=$(aws elbv2 create-target-group --name thumbs-tg --target-type lambda \
  --query 'TargetGroups[0].TargetGroupArn' --output text)
aws lambda add-permission --function-name thumbs:live --statement-id alb-invoke \
  --principal elasticloadbalancing.amazonaws.com --action lambda:InvokeFunction \
  --source-arn "$TG_ARN"
aws elbv2 register-targets --target-group-arn "$TG_ARN" \
  --targets Id=arn:aws:lambda:eu-west-1:111122223333:function:thumbs:live

The function receives the request as a JSON event (httpMethod, path, queryStringParameters, headers, body, isBase64Encoded) and must return:

{ "statusCode": 200, "statusDescription": "200 OK", "isBase64Encoded": false,
  "headers": { "Content-Type": "application/json" }, "body": "{\"ok\": true}" }

Reading client details behind an ALB (Flask example):

from flask import request

xff = request.headers.get("X-Forwarded-For", "")      # "203.0.113.7" or "spoofed-value, 203.0.113.7"
peer_ip = xff.split(",")[-1].strip()                   # last entry = the address the ALB actually saw
scheme = request.headers.get("X-Forwarded-Proto")      # "https" when the client used TLS
trace = request.headers.get("X-Amzn-Trace-Id")         # correlate with access logs / X-Ray

Static IPs for partners, deployments and troubleshooting:

# NLB with one Elastic IP per AZ; security groups only possible if attached now
# (omit --security-groups if you will need QUIC or TCP_QUIC listeners)
aws elbv2 create-load-balancer --name partner-nlb --type network --scheme internet-facing \
  --subnet-mappings SubnetId=subnet-0aaa1111bbbb2222c,AllocationId=eipalloc-0aaa1111bbbb2222c \
                    SubnetId=subnet-0ddd3333eeee4444f,AllocationId=eipalloc-0ddd3333eeee4444f \
  --security-groups sg-0123456789abcdef0

# Faster deployments: drain for 30 s instead of 300 s
aws elbv2 modify-target-group-attributes --target-group-arn "$ORDERS_TG_ARN" \
  --attributes Key=deregistration_delay.timeout_seconds,Value=30

# Why is a target out of service? (Target.ResponseCodeMismatch, Target.Timeout, ...)
aws elbv2 describe-target-health --target-group-arn "$ORDERS_TG_ARN" \
  --query 'TargetHealthDescriptions[].[Target.Id,TargetHealth.State,TargetHealth.Reason]'

# Access logs to S3 (bucket in the same Region, SSE-S3, bucket policy below)
aws elbv2 modify-load-balancer-attributes --load-balancer-arn "$ALB_ARN" \
  --attributes Key=access_logs.s3.enabled,Value=true Key=access_logs.s3.bucket,Value=my-alb-logs

# Alarm on errors generated by the load balancer itself
aws cloudwatch put-metric-alarm --alarm-name orders-alb-elb-5xx \
  --namespace AWS/ApplicationELB --metric-name HTTPCode_ELB_5XX_Count \
  --dimensions Name=LoadBalancer,Value=app/orders-alb/50dc6c495c0c9188 \
  --statistic Sum --period 60 --evaluation-periods 3 --threshold 10 \
  --comparison-operator GreaterThanThreshold --treat-missing-data notBreaching

Access-log bucket policy (current form):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "logdelivery.elasticloadbalancing.amazonaws.com" },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::my-alb-logs/AWSLogs/111122223333/*"
    }
  ]
}

Traps

Practice questions

  1. A company exposes a TCP-based pricing API to partner companies. Each partner must allow-list a fixed set of destination IP addresses in its firewall, the API must absorb sudden spikes of millions of requests per second with minimal latency, and the application must log each partner's real source IP address. Which solution meets these requirements?

    • A. An Application Load Balancer with an Elastic IP address in each subnet; the application reads X-Forwarded-For.
    • B. A Network Load Balancer with an Elastic IP address per Availability Zone and instance-type targets.
    • C. A Gateway Load Balancer with the API instances registered as appliances behind a GWLB endpoint.
    • D. A Classic Load Balancer with cross-zone load balancing and proxy protocol enabled.
  2. An application behind an Application Load Balancer returns intermittent HTTP 502 errors. CloudWatch shows HTTPCode_ELB_5XX_Count increasing while HTTPCode_Target_5XX_Count stays at zero. Which TWO causes are consistent with these symptoms? (Select TWO.)

    • A. The web server's keep-alive timeout is shorter than the load balancer's idle timeout, so it closes connections the ALB is still reusing.
    • B. The target group currently has no registered targets.
    • C. The application sometimes writes malformed HTTP response headers.
    • D. An AWS WAF web ACL attached to the load balancer blocks some requests.
    • E. Clients disconnect before the load balancer sends the response.
Answers
  1. B. Only an NLB gives a static (Elastic) IP per AZ, handles millions of requests per second, and preserves the client IP for instance targets. A fails because you cannot attach Elastic IPs to an ALB; C is for transparent inspection appliances, not for serving an API; D has no static IPs and is the legacy, lower-performance option.
  2. A, C. Both make the load balancer itself return 502 while the targets never report a 5XX. B produces 503; D produces 403 (an ELB 4XX code); E is logged as 460 (also ELB 4XX).

9. Sticky sessions, TLS certificates and SNI

Concept

Exam facts

Sticky sessions

ACM certificates

HTTPS / TLS listeners

Offload vs end-to-end encryption

Developer angle

Request a certificate, attach it to an HTTPS listener with an explicit modern policy, then add more domains for SNI:

# Same Region as the ALB (use --region us-east-1 instead for a CloudFront distribution)
aws acm request-certificate --domain-name shop.example.com \
  --subject-alternative-names www.shop.example.com --validation-method DNS --region eu-west-1
aws acm describe-certificate --certificate-arn "$SHOP_CERT_ARN" --region eu-west-1 \
  --query 'Certificate.DomainValidationOptions[].ResourceRecord'   # CNAME to create in DNS

# HTTPS listener: default certificate + explicit policy (the CLI default is ELBSecurityPolicy-2016-08)
aws elbv2 create-listener --load-balancer-arn "$ALB_ARN" --protocol HTTPS --port 443 \
  --certificates CertificateArn="$SHOP_CERT_ARN" \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-Res-PQ-2025-09 \
  --default-actions Type=forward,TargetGroupArn="$WEB_TG_ARN"

# More domains on the same listener (SNI certificate list)
aws elbv2 add-listener-certificates --listener-arn "$HTTPS_LISTENER_ARN" \
  --certificates CertificateArn="$BLOG_CERT_ARN" CertificateArn="$API_CERT_ARN"

# Port 80 only redirects to HTTPS
aws elbv2 create-listener --load-balancer-arn "$ALB_ARN" --protocol HTTP --port 80 \
  --default-actions '[{"Type":"redirect","RedirectConfig":{"Protocol":"HTTPS","Port":"443","StatusCode":"HTTP_301"}}]'

# Which protocols/ciphers does a policy allow?
aws elbv2 describe-ssl-policies --names ELBSecurityPolicy-TLS13-1-2-Res-PQ-2025-09

Check which certificate a hostname receives (SNI on the wire):

openssl s_client -connect shop.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -dates

Stickiness on an ALB target group:

# Duration-based: load balancer cookie AWSALB, 1 hour
aws elbv2 modify-target-group-attributes --target-group-arn "$WEB_TG_ARN" --attributes \
  Key=stickiness.enabled,Value=true Key=stickiness.type,Value=lb_cookie \
  Key=stickiness.lb_cookie.duration_seconds,Value=3600

# Application-based: follow the app's own session cookie (name must not start with AWSALB)
aws elbv2 modify-target-group-attributes --target-group-arn "$WEB_TG_ARN" --attributes \
  Key=stickiness.enabled,Value=true Key=stickiness.type,Value=app_cookie \
  Key=stickiness.app_cookie.cookie_name,Value=SESSIONID \
  Key=stickiness.app_cookie.duration_seconds,Value=3600

The scalable alternative: keep the session outside the instance (DynamoDB table with TTL on expires_at):

import time
import uuid
import boto3

sessions = boto3.resource("dynamodb").Table("web-sessions")

def save_session(cart):
    session_id = str(uuid.uuid4())
    sessions.put_item(Item={"session_id": session_id, "cart": cart,
                            "expires_at": int(time.time()) + 3600})
    return session_id

Mutual TLS in verify mode:

aws elbv2 create-trust-store --name partner-ca \
  --ca-certificates-bundle-s3-bucket my-pki-bucket --ca-certificates-bundle-s3-key partner-ca-bundle.pem
aws elbv2 modify-listener --listener-arn "$HTTPS_LISTENER_ARN" \
  --mutual-authentication Mode=verify,TrustStoreArn="$TRUST_STORE_ARN"

Traps

Practice questions

  1. A company runs a web application on EC2 instances in an Auto Scaling group behind an Application Load Balancer. Shopping carts are kept in each instance's memory, and users report empty carts after scale-in events and instance failures. The company wants the MOST scalable and resilient solution. What should a developer do?

    • A. Enable duration-based stickiness on the target group with a 7-day duration.
    • B. Store session data in Amazon ElastiCache or Amazon DynamoDB and keep the instances stateless.
    • C. Enable application-based stickiness that follows the application's session cookie.
    • D. Turn on scale-in protection for every instance in the Auto Scaling group.
  2. A developer must serve shop.example.com and blog.example.org over HTTPS on port 443 from a single internet-facing load balancer in eu-west-1. Each domain needs its own publicly trusted certificate, and operational overhead must be as low as possible. Which TWO actions should the developer take? (Select TWO.)

    • A. Use an Application Load Balancer with one HTTPS listener: one certificate as the default and the other in the listener's certificate list, so SNI selects the right one.
    • B. Use a Classic Load Balancer with one HTTPS listener and attach both certificates to it.
    • C. Request both public certificates from ACM in eu-west-1 with DNS validation.
    • D. Request both certificates from ACM in us-east-1, because load balancer certificates are global.
    • E. Import certificates from a third-party CA into IAM and rotate them manually before they expire.
Answers
  1. B. An external session store survives instance loss and lets any instance serve any user, so the fleet can scale freely. A and C still lose carts when the sticky instance terminates and they skew load; D blocks scale-in (cost) and does not help when instances fail.
  2. A, C. An ALB listener holds a default certificate plus a certificate list selected by SNI, and same-Region, DNS-validated ACM certificates renew automatically. B fails because a CLB listener takes one certificate and has no SNI; D applies only to CloudFront; E works but adds manual renewal work.

10. Auto Scaling: policies, health checks and instance refresh

Concept

Exam facts

Capacity, launch templates, health checks

Scaling policies

Instance refresh (StartInstanceRefresh)

Lifecycle hooks

Termination, protection, troubleshooting, cost

Developer angle

Wire the group to the load balancer, use ELB health checks, a realistic grace period and a default warmup:

aws autoscaling attach-traffic-sources --auto-scaling-group-name web-asg \
  --traffic-sources Identifier="$WEB_TG_ARN",Type=elbv2
aws autoscaling update-auto-scaling-group --auto-scaling-group-name web-asg \
  --health-check-type ELB --health-check-grace-period 300 --default-instance-warmup 120
aws autoscaling enable-metrics-collection --auto-scaling-group-name web-asg --granularity "1Minute"

Target tracking on requests per target, and a scheduled weekly peak:

aws autoscaling put-scaling-policy --auto-scaling-group-name web-asg \
  --policy-name rps-per-target --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ALBRequestCountPerTarget",
      "ResourceLabel": "app/web-alb/50dc6c495c0c9188/targetgroup/web-tg/73e2d6bc24d8a067"
    },
    "TargetValue": 500.0
  }'

aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name web-asg \
  --scheduled-action-name friday-peak --recurrence "0 17 * * 5" --time-zone "Europe/Berlin" \
  --min-size 10 --max-size 30

Queue workers: target tracking on SQS backlog per instance with metric math. GroupInServiceInstances exists only when group metrics are enabled; without it m2 has no data, the policy's alarms stay in INSUFFICIENT_DATA and the group never scales:

aws autoscaling enable-metrics-collection --auto-scaling-group-name worker-asg --granularity "1Minute"
aws autoscaling put-scaling-policy --auto-scaling-group-name worker-asg \
  --policy-name sqs-backlog --policy-type TargetTrackingScaling \
  --target-tracking-configuration file://sqs-backlog.json

sqs-backlog.json:

{
  "CustomizedMetricSpecification": {
    "Metrics": [
      { "Id": "m1", "ReturnData": false,
        "MetricStat": { "Metric": { "Namespace": "AWS/SQS", "MetricName": "ApproximateNumberOfMessagesVisible",
          "Dimensions": [ { "Name": "QueueName", "Value": "image-jobs" } ] }, "Stat": "Sum" } },
      { "Id": "m2", "ReturnData": false,
        "MetricStat": { "Metric": { "Namespace": "AWS/AutoScaling", "MetricName": "GroupInServiceInstances",
          "Dimensions": [ { "Name": "AutoScalingGroupName", "Value": "worker-asg" } ] }, "Stat": "Average" } },
      { "Id": "e1", "Expression": "m1 / m2", "Label": "Backlog per instance", "ReturnData": true }
    ]
  },
  "TargetValue": 100
}

Roll out launch template version 8 safely (aws autoscaling start-instance-refresh --cli-input-json file://refresh.json):

{
  "AutoScalingGroupName": "web-asg",
  "DesiredConfiguration": {
    "LaunchTemplate": { "LaunchTemplateName": "web-lt", "Version": "8" }
  },
  "Preferences": {
    "MinHealthyPercentage": 100,
    "MaxHealthyPercentage": 110,
    "InstanceWarmup": 120,
    "SkipMatching": true,
    "CheckpointPercentages": [25, 100],
    "CheckpointDelay": 900,
    "AutoRollback": true,
    "AlarmSpecification": { "Alarms": ["web-alb-elb-5xx-high"] }
  }
}
aws autoscaling describe-instance-refreshes --auto-scaling-group-name web-asg
aws autoscaling rollback-instance-refresh --auto-scaling-group-name web-asg   # only while in progress

Launch hook that waits for bootstrapping; the last lines of the user data script release the instance (IMDSv2, §2):

aws autoscaling put-lifecycle-hook --auto-scaling-group-name web-asg \
  --lifecycle-hook-name wait-for-bootstrap \
  --lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING \
  --heartbeat-timeout 600 --default-result ABANDON

# ... at the end of user data, after the app passed a local smoke test:
TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
aws autoscaling complete-lifecycle-action --auto-scaling-group-name web-asg \
  --lifecycle-hook-name wait-for-bootstrap --instance-id "$INSTANCE_ID" \
  --lifecycle-action-result CONTINUE

The instance profile role needs permission for that call:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["autoscaling:CompleteLifecycleAction", "autoscaling:RecordLifecycleActionHeartbeat"],
      "Resource": "arn:aws:autoscaling:eu-west-1:111122223333:autoScalingGroup:*:autoScalingGroupName/web-asg"
    }
  ]
}

Debug an instance that keeps being replaced:

aws autoscaling describe-scaling-activities --auto-scaling-group-name web-asg --max-items 5
aws autoscaling suspend-processes --auto-scaling-group-name web-asg --scaling-processes ReplaceUnhealthy
# ... inspect the instance, fix the health check path or security group ...
aws autoscaling resume-processes --auto-scaling-group-name web-asg --scaling-processes ReplaceUnhealthy

# Alternative: take one instance out of service (deregistered, no health checks, still billed)
aws autoscaling enter-standby --auto-scaling-group-name web-asg \
  --instance-ids i-0123456789abcdef0 --should-decrement-desired-capacity
aws autoscaling exit-standby --auto-scaling-group-name web-asg --instance-ids i-0123456789abcdef0

Traps

Practice questions

  1. A team runs 20 instances in an Auto Scaling group behind an Application Load Balancer and has created launch template version 8 with a patched AMI. The rollout must never drop below current serving capacity, must pause after a quarter of the instances are replaced so testers can verify them, and must revert automatically if the ALB 5XX CloudWatch alarm enters the ALARM state. Which approach meets these requirements?

    • A. Point the Auto Scaling group at the $Latest template version and terminate five instances at a time from the console.
    • B. Start an instance refresh with a desired configuration of version 8, MinHealthyPercentage 100, MaxHealthyPercentage 110, CheckpointPercentages [25, 100], AutoRollback enabled, and the alarm in AlarmSpecification.
    • C. Start an instance refresh with MinHealthyPercentage 75, skip matching disabled, and a bake time of one hour.
    • D. Change the termination policy to OldestLaunchTemplate and let normal scale-in activity replace the instances.
  2. A developer created an Auto Scaling group with the AWS CLI, attached it to an ALB target group, and set the health check type to ELB. New instances need about four minutes to finish their user data bootstrapping, and the group keeps terminating and replacing them before they ever serve traffic. Which TWO changes will stop the replacement loop? (Select TWO.)

    • A. Set the health check grace period to a value longer than the bootstrap time.
    • B. Add a launch lifecycle hook and call complete-lifecycle-action at the end of the user data script.
    • C. Increase the group's default cooldown to 600 seconds.
    • D. Replace the target tracking policy with a step scaling policy.
    • E. Increase the target group's deregistration delay to 3600 seconds.
Answers
  1. B. MinHealthyPercentage 100 launches replacements before terminating, the 25% checkpoint pauses for testing, and AutoRollback with the alarm reverts automatically. A is manual, drops capacity and has no rollback; C lets capacity fall to 75% with no checkpoint or rollback; D replaces instances only when scale-in happens, with no control or rollback.
  2. A, B. A grace period longer than the boot time stops premature unhealthy verdicts, and a launch hook keeps the instance in Pending:Wait (not yet registered or health-checked) until bootstrapping finishes. C gates simple scaling policies and never delays the replacement of unhealthy instances; D does not change health checking; E only affects draining on deregistration.

11. CloudWatch for EC2: metrics, agent and status checks

Concept

Exam facts

Check Watches Typical causes Your move
System (StatusCheckFailed_System) AWS host and network Loss of network connectivity or power, host software or hardware fault Wait for AWS, recover, or stop/start an EBS-backed instance (new host); replace an instance-store-backed one
Instance (StatusCheckFailed_Instance) Your OS answering ARP on its network interface Wrong network or startup configuration, exhausted memory, corrupted file system, incompatible kernel; also fails while the system check fails Reboot or fix the configuration (aws ec2 get-console-output helps)
Attached EBS (StatusCheckFailed_AttachedEBS) Attached EBS volumes reachable and completing I/O (Nitro) Storage subsystem faults, host-to-EBS connectivity issues Wait, replace the affected volume, or stop/start; an ASG can replace the instance

Developer angle

Detailed monitoring and a high-resolution custom metric:

aws ec2 monitor-instances --instance-ids i-0123456789abcdef0     # 1-minute metrics (undo: unmonitor-instances)

aws cloudwatch put-metric-data --namespace OrdersApp --metric-name QueueLagMs \
  --dimensions InstanceId=i-0123456789abcdef0,Env=prod \
  --unit Milliseconds --value 87 --storage-resolution 1
import boto3

cloudwatch = boto3.client("cloudwatch")  # credentials come from the instance role
cloudwatch.put_metric_data(
    Namespace="OrdersApp",
    MetricData=[{
        "MetricName": "QueueLagMs",
        "Dimensions": [{"Name": "Env", "Value": "prod"}],
        "Unit": "Milliseconds",
        "Value": 87.0,
        "StorageResolution": 1,
    }],
)

CloudWatch agent config for memory, root-disk usage and an application log (cwagent.json):

{
  "agent": { "metrics_collection_interval": 60 },
  "metrics": {
    "append_dimensions": { "InstanceId": "${aws:InstanceId}" },
    "metrics_collected": {
      "mem": { "measurement": ["mem_used_percent"] },
      "disk": { "measurement": ["used_percent"], "resources": ["/"] }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/orders/app.log",
            "log_group_name": "/orders/app",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 30
          }
        ]
      }
    }
  }
}
# Store once (admin with CloudWatchAgentAdminPolicy or ssm:PutParameter); name must start with AmazonCloudWatch-
aws ssm put-parameter --name AmazonCloudWatch-orders-linux --type String --value file://cwagent.json
# On each instance (role: CloudWatchAgentServerPolicy)
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config -m ec2 -s -c ssm:AmazonCloudWatch-orders-linux

Alarms that recover the instance and watch agent memory, plus a notification test:

aws cloudwatch put-metric-alarm --alarm-name web-1-recover \
  --namespace AWS/EC2 --metric-name StatusCheckFailed_System \
  --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
  --statistic Maximum --period 60 --evaluation-periods 2 --datapoints-to-alarm 2 \
  --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions arn:aws:automate:eu-west-1:ec2:recover arn:aws:sns:eu-west-1:111122223333:ops-alerts

# Agent metric: namespace CWAgent; dimensions must match what the agent publishes
aws cloudwatch put-metric-alarm --alarm-name web-1-mem-high \
  --namespace CWAgent --metric-name mem_used_percent \
  --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
  --statistic Average --period 60 --evaluation-periods 5 --datapoints-to-alarm 3 \
  --threshold 90 --comparison-operator GreaterThanThreshold \
  --alarm-actions arn:aws:sns:eu-west-1:111122223333:ops-alerts

# Fires the alarm's actions for real: use it on SNS-only alarms
aws cloudwatch set-alarm-state --alarm-name web-1-mem-high \
  --state-value ALARM --state-reason "notification test"

Traps

Practice questions

  1. An application on EC2 measures queue latency every 10 seconds. The team wants a CloudWatch alarm that can enter ALARM within 30 seconds of a latency spike. What should the developer do?

    • A. Enable detailed monitoring on the instance and create an alarm with a 60-second period.
    • B. Publish the metric with PutMetricData using StorageResolution 1 and create an alarm with a 10-second period.
    • C. Publish the metric with StorageResolution 60 and create an alarm with a 10-second period.
    • D. Create a composite alarm that evaluates the latency metric every 10 seconds.
  2. A CloudWatch alarm reports StatusCheckFailed_System = 1 for an EBS-backed m7i instance that is not part of an Auto Scaling group. Which TWO actions can restore the instance on healthy hardware? (Select TWO.)

    • A. Stop the instance and then start it again.
    • B. Reboot the instance from the EC2 console.
    • C. Configure a CloudWatch alarm on StatusCheckFailed_System with the EC2 recover action.
    • D. Install the CloudWatch agent and restart the application.
    • E. Add an inbound rule for ICMP to the instance's security group.
Answers
  1. B. Sub-minute alarms (10, 20 or 30 s) need a high-resolution custom metric (StorageResolution 1). A is limited to 1-minute data; C stores 1-minute data, so a 10-second alarm drifts into INSUFFICIENT_DATA; D is wrong because composite alarms only combine other alarms' states.
  2. A, C. A system check failure is an AWS host problem: stop/start moves an EBS-backed instance to a new host, and the recover action migrates it while keeping its ID and IPs. B keeps it on the same host; D and E don't touch the hardware fault.

Quick decision rules

IAM roles, IMDS and user data (§1–§3)

Connectivity and access (§4)

Purchasing options and Spot (§5)

EBS, AMIs and storage choice (§6–§7)

Load balancing, TLS and Auto Scaling (§8–§10)

CloudWatch for EC2 (§11)

Changed vs older material

Learn the Current column; older practice material may still show the Older material may say value. The same rows, with verification dates, live in notes/current-facts.md.

Topic Older material may say Current (verified 2026-10-12) Source
§1 · Credentials for CLI/SDK use outside AWS Use environment variables or named profiles with IAM user access keys Prefer temporary credentials: aws login with console credentials (AWS CLI 2.32.0+, refreshed every 15 min, session up to 12 h), IAM Identity Center (aws configure sso) or IAM Roles Anywhere for servers; long-term keys only where roles are impossible AWS docs
§1 · AWS CLI v2 credential precedence 6 sources: command-line options, environment, credentials file, config file, container, instance profile 10 sources: options → environment → assume role → web identity → IAM Identity Center → credentials file → credential_process → config file → container (ECS task role) → EC2 instance profile AWS docs
§1 · AWS SDK for Java default credential chain Java 1.x: system properties aws.accessKeyId / aws.secretKey, 5-step chain Java 2.x DefaultCredentialsProvider: aws.accessKeyId / aws.secretAccessKey / aws.sessionToken → environment variables → web identity token → shared credentials/config files → ECS container → instance profile (Java 1.x end of support 2025-12-31) AWS docs
§2 · IMDS default metadata version (HttpTokens) IMDSv1 is the default and both versions are always available IMDSv2-only by default for AL2023 AMIs (ImdsSupport v2.0: required, hop limit 2) and console Quick Start launches ("V2 only"). Accounts can opt in to a per-Region default of required (March 2024) and enforce it (February 2026). With nothing set at launch, account (default "No preference") or AMI level, HttpTokens is optional AWS docs
§2 · IMDSv2 default for newly released instance types AWS's November 2023 roadmap: newly released instance types use IMDSv2 only by default from mid-2024 Current EC2 docs define no instance-type default; precedence is launch parameters → account default → AMI, and "not set" everywhere resolves to V1 or V2 (optional); check the instance's MetadataOptions AWS docs
§2 · Blocking launches that allow IMDSv1 (account-level enforcement) Only IAM or SCP condition keys (ec2:MetadataHttpTokens) can block launches that allow IMDSv1 Since 2026-02-24, modify-instance-metadata-defaults --http-tokens-enforced enabled (or a declarative policy) fails any launch whose HttpTokens does not resolve to required; setting optional on an existing instance fails with UnsupportedOperation; instances that already allow IMDSv1 are untouched AWS docs
§2 · AL2023 instance metadata defaults (ImdsSupport v2.0) Instances default to IMDSv1 allowed (HttpTokens optional) with a PUT response hop limit of 1 AL2023 AMIs carry ImdsSupport v2.0: instances default to HttpTokens required with hop limit 2 (an account-level hop limit overrides the AMI value) AWS docs
§3 · AL2023 cloud-init behaviour and package manager Amazon Linux 2: cloud-init installs security updates at launch, unreachable remote includes are ignored, package manager yum AL2023: no launch-time security updates by default, unavailable remote resources fail cloud-init ("fail closed"), package manager DNF AWS docs
§3 · AL2023 deterministic upgrades and boot-time version drift Every boot-time yum install pulls the newest packages, so instances drift between launches Versioned repositories are on by default: dnf installs from the Amazon Linux repositories lock to the AMI's release; drift comes from a changing AMI (latest-AMI SSM parameter), --releasever=latest upgrades, or unpinned pip/npm/third-party sources AWS docs
§4 · Security group scope (VPC associations and sharing) A security group is locked to the single VPC it was created in Still Region-scoped, but it can be associated with other VPCs in the same Region (Security Group VPC Associations) and shared with accounts in the same AWS Organization (AWS RAM, same Region) AWS docs
§4 · Public IPv4 and Elastic IP charge An Elastic IP is free while attached to a running instance Since 1 Feb 2024 every public IPv4 address, in use or idle, including an Elastic IP on a running instance, is billed $0.005 per IP-hour AWS docs
§4 · EC2 Instance Connect IP address support EC2 Instance Connect needs a public IPv4 address Since 23 Sep 2024 it also connects to a public IPv6 address; the instance needs a public IPv4 or IPv6 address in a subnet with a route to the internet. Browser-client prefix lists: com.amazonaws.<region>.ec2-instance-connect (IPv4) and com.amazonaws.<region>.ipv6.ec2-instance-connect (IPv6). Inbound TCP 22 is still required. AWS docs
§4 · EC2 Instance Connect preinstalled AMIs EC2 Instance Connect works out of the box only on Amazon Linux 2 Preinstalled on the AL2023 standard AMI, Amazon Linux 2 2.0.20190618 or later, Ubuntu 20.04 or later, and macOS Sonoma 14.2.1+, Ventura 13.6.3+ and Monterey 12.7.2+ AWS docs
§4 · EC2 Instance Connect Endpoint EC2 Instance Connect always needs a public IP; private instances need a bastion Since June 2023 an EIC Endpoint reaches instances by private IPv4 address (IPv6 and dual-stack endpoints since October 2025) with no public IP, internet gateway or bastion. The instance SG must still allow 22 or 3389 from the endpoint SG; IAM action ec2-instance-connect:OpenTunnel. Limits: 1 hour per connection, 20 concurrent connections, 1 endpoint per VPC and per subnet, 5 per account per Region. No extra charge. AWS docs · quotas
§5 · On-Demand per-second billing by operating system Only Linux and Windows are billed per second; every other OS is billed hourly Per second with a 60 s minimum for Linux, Windows (including SQL Server editions), RHEL and Ubuntu Pro; only SLES is billed per full hour AWS pricing
§5 · Savings Plans returns A Savings Plan can never be cancelled or returned Since 20 Mar 2024 a Savings Plan with an hourly commitment of $100 or less can be returned within 7 days of purchase and in the same calendar month; up to 10 returns per calendar year per management account; upfront payments are refunded in full (ReturnSavingsPlan API). Otherwise the commitment can't be changed or cancelled. AWS docs
§5 · Savings Plans types Only Compute, EC2 Instance and SageMaker Savings Plans exist Four types: Compute (up to 66%), EC2 Instance (up to 72%, one family in one Region), SageMaker AI (up to 64%) and Database Savings Plans (up to 35% across Aurora, RDS, DynamoDB, ElastiCache and other database services) AWS docs
§5 · Spot pricing model and interruptions Spot is an auction: you lose the instance when outbid or when the price passes your bid, so bid high to keep it There is no bidding. EC2 interrupts mainly to reclaim capacity. The max price defaults to the On-Demand price, setting one only increases interruptions, and you always pay the current Spot price. AWS docs
§5 · Spot request APIs Spot Fleet, RequestSpotInstances and Spot blocks are the main Spot tools Spot Fleet (RequestSpotFleet) and RequestSpotInstances are legacy APIs with no planned investment, and AWS strongly discourages both. Use Auto Scaling groups, EC2 Fleet (instant type when no scaling is needed) or RunInstances with Spot market options. Defined-duration Spot blocks are deprecated. AWS docs · Spot requests
§5 · Spot allocation strategy recommendation lowest-price (or capacity-optimized) is the strategy to choose price-capacity-optimized is recommended. lowest-price is marked "not recommended" (highest risk of interruption) but is still the API default for SpotAllocationStrategy, and in ASGs it is available only through the AWS CLI/API. AWS docs
§5 · Future-dated Capacity Reservations Capacity Reservations never involve a commitment and can always be cancelled freely Since 21 Nov 2024 future-dated reservations (requested 5–120 days ahead) need a commitment duration (minimum 14 days, or 12 weeks for U and X instances) and at least 32 vCPUs, and a cancellation charge may apply. Immediate-use reservations are still commitment-free. AWS docs
§6 · gp3 maximum size, IOPS and throughput 16 TiB, 16,000 IOPS, 1,000 MiB/s 64 TiB, 80,000 IOPS (500 IOPS per GiB), 2,000 MiB/s (0.25 MiB/s per IOPS), since 2025-09-26 AWS docs
§6 · io2 vs io2 Block Express io2 is limited to 64,000 IOPS and 16 TiB unless it is Block Express All io2 volumes are io2 Block Express since 2025-04-30: 4 GiB–64 TiB, 256,000 IOPS (1,000 per GiB), 4,000 MiB/s, 99.999% durability AWS docs
§6 · IOPS threshold for Provisioned IOPS volumes Any requirement above 16,000 IOPS needs io1/io2 gp3 covers up to 80,000 IOPS and 2,000 MiB/s; io2 Block Express is for more than that, sub-millisecond latency, 99.999% durability or Multi-Attach AWS docs
§6 · Elastic Volumes modification frequency Wait at least 6 hours between modifications of the same volume A new modification can start when the previous one completes, up to 4 per volume per rolling 24 hours (since 2026-01-15) AWS docs
§6 · EBS encryption instance support Only some instance types support EBS encryption Available on all current and previous generation instance types AWS docs
§6 · Recycle Bin coverage Snapshots and AMIs only Also deleted EBS volumes (retention 1–7 days), since 2025-11-20 AWS docs
§6 · Volume initialization from snapshots Fast Snapshot Restore and manual pre-warming are the only options Provisioned Rate for Volume Initialization of 100–300 MiB/s per volume (since 2025-05-06) AWS docs
§6 · Duplicating an EBS volume (copy-volumes) A snapshot is the only way to duplicate a volume copy-volumes makes an instant, crash-consistent same-AZ copy that can encrypt or re-key it (since 2025-10-14). VolumeType defaults to gp2. Background initialization runs at the lowest of 3,000 IOPS / 125 MiB/s and both volumes' provisioned levels. 1 copy per source at a time, at most 5 in progress per Region. AWS docs
§6 · Giving another account a volume's data Only by sharing a snapshot Volumes can be shared through AWS RAM (AWSRAMPermissionEBSVolumeCopyAccess); the other account can only view and copy them in the same AZ, never attach them. Unencrypted or customer managed key only; aws/ebs volumes cannot be shared. AWS docs
§6 · Deregistering an AMI and its snapshots Snapshots always have to be deleted separately Snapshots are still kept by default, but deregister-image --delete-associated-snapshots deletes them in the same call (since 2025-05-28) AWS docs
§6 · Amazon Linux 2 AMI parameter /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 AL2 reached end of support on 2026-06-30; use the AL2023 SSM parameters (al2023-ami-kernel-default-x86_64 / -arm64) AWS FAQ
§6 · Root volume replacement sources Only the launch state, a snapshot of the root, or an AMI create-replace-root-volume-task --volume-id swaps in an existing available, detached same-AZ volume (since 2026-07-09). DeleteOnTermination does not carry over; encrypted → unencrypted is rejected. AWS docs
§7 · EFS file system type naming EFS Standard (multi-AZ) and One Zone / One Zone-IA as availability options File system types are Regional (≥ 3 AZs) and One Zone; "EFS Standard" now names the SSD storage class AWS docs
§7 · EFS storage classes and small files Only Standard and IA classes; files smaller than 128 KiB never leave Standard Standard, Infrequent Access and Archive; files under 128 KiB are tiered (billed as 128 KiB) for lifecycle policies set since 2023-11-26 AWS docs
§7 · EFS Max I/O performance mode Choose Max I/O for highly parallel big-data or media workloads Max I/O is previous generation, not supported with Elastic throughput or One Zone; AWS recommends General Purpose for all file systems AWS docs
§7 · EFS Elastic throughput limits Up to 3 GiB/s read and 1 GiB/s write 20–60 GiB/s read and 1–5 GiB/s write per file system (Region-dependent); 1,500 MiB/s per client with amazon-efs-utils 2.0+ (500 MiB/s otherwise) AWS docs
§7 · EFS default throughput mode and encryption Bursting is the default everywhere The console defaults to Elastic, General Purpose and encryption at rest; the CreateFileSystem API/CLI still defaults to bursting and no encryption unless requested AWS docs
§7 · Mounting S3 as a file system (S3 Files) S3 cannot be mounted as a POSIX file system with locking Amazon S3 Files (built on EFS) mounts a bucket over NFS 4.1/4.2 with POSIX permissions and file locking from EC2, Lambda, ECS and EKS AWS docs
§8 · NLB security groups NLBs have no security groups Supported since 10 Aug 2023, but only if at least one is attached at creation (cannot be added later); QUIC/TCP_QUIC listeners require an NLB with no security groups AWS docs
§8 · NLB listener protocols TCP, TLS and UDP only TCP, TLS, UDP, TCP_UDP, QUIC and TCP_QUIC (QUIC since 13 Nov 2025; QUIC needs an ipv4 NLB without SGs, fixed 300 s deregistration delay, no Proxy Protocol v2) AWS docs
§8 · NLB TCP idle timeout Fixed at 350 s Default 350 s, configurable 60–6000 s per TCP listener (tcp.idle_timeout.seconds); TLS listeners stay at a fixed 350 s AWS docs
§8 · ALB rule condition matching Only * and ? wildcards host-header, path-pattern and http-header conditions also accept regular expressions (RegexValues) AWS docs
§8 · ALB public IPv4 addresses ALB public IPs are always AWS-chosen; only an NLB gives allow-listable addresses An internet-facing IPv4 ALB can draw public IPv4 addresses from an Amazon VPC IPAM pool (e.g. BYOIP) via modify-ip-pools; not for internal ALBs; falls back to AWS-managed IPs when the pool is exhausted; an NLB is still needed for fixed per-AZ IPs AWS docs
§8 · CLB cross-zone default CLB: cross-zone load balancing disabled by default Disabled by default via API/CLI, enabled by default in the console; no inter-AZ charge AWS docs
§8 · ALB access-log bucket policy Grant s3:PutObject to a Region-specific ELB account ID; S3 is the only destination Grant the service principal logdelivery.elasticloadbalancing.amazonaws.com (supported in all Regions since 10 Sep 2025; the legacy policy still works); ALB logs can also go out as CloudWatch vended logs AWS docs
§9 · ACM public certificate validity and renewal 13 months (395 days), renewal from 60 days before expiry 198 days, managed renewal 45 days before expiry (since February 2026); the CA/B Forum maximum falls to 100 days from 15 Mar 2027 and 47 days from 15 Mar 2029 (ACM's future values not yet documented) What's New
§9 · ACM exportable public certificates ACM public certificates and their private keys can never leave ACM Exportable public certificates since 17 Jun 2025 (certificate, key and chain); $7 per FQDN / $79 per wildcard at issuance and each renewal since February 2026 (launched at $15/$149); standard certificates are still not exportable AWS pricing
§9 · Default ELB TLS security policy ELBSecurityPolicy-2016-08 (or ELBSecurityPolicy-TLS13-1-2-2021-06) is the default everywhere Console: ELBSecurityPolicy-TLS13-1-2-Res-PQ-2025-09; CLI, CloudFormation and CDK: ELBSecurityPolicy-2016-08 (allows TLS 1.0/1.1), so set SslPolicy explicitly AWS docs
§10 · Launch configurations Legacy but still available No new instance types since 1 Jan 2023; no console creation for accounts created on or after 1 Jun 2023; no creation by any method for accounts created on or after 1 Oct 2024 AWS docs
§10 · Auto Scaling health check types Only EC2 and ELB health checks EC2 (default, always on), ELB, VPC Lattice and EBS health checks, plus custom health (set-instance-health) AWS docs
§10 · Detecting application failures in an ASG Only ELB health checks detect application failures EC2 application status checks (HTTP/HTTPS every 60 s, thresholds 2/2, 6 s timeout, matcher 200, 300 s initialization grace; associated by tag aws:autoscaling:groupName) make Auto Scaling replace impaired instances even with health check type EC2; no redirects followed; $0.01 per managed ENI-hour per AZ AWS docs
§10 · SQS backlog-per-instance scaling Publish backlog per instance yourself with PutMetricData Target tracking computes it with metric math (ApproximateNumberOfMessagesVisible / GroupInServiceInstances); group metrics must be enabled AWS docs
§10 · Scaling cooldown scope During the cooldown the ASG launches or terminates nothing The default 300 s cooldown gates simple scaling; target tracking and step scaling scale out without it; scheduled actions and unhealthy-instance replacement ignore it; scale-in can still be delayed AWS docs
§10 · Instance refresh capacity behaviour An instance refresh always terminates first, dropping capacity to the minimum healthy percentage MinHealthyPercentage 100% launches replacements before terminating; MaxHealthyPercentage 100–200% (max − min ≤ 100) replaces more at once AWS docs
§10 · Termination lifecycle hook outcome A termination hook can never stop the termination (with ABANDON or CONTINUE, the instance terminates) An instance lifecycle policy (since 20 Nov 2025) can retain the instance on TerminateHookAbandon in Terminating:Retained (still billed, outside desired capacity; terminate it with terminate-instance-in-auto-scaling-group) AWS docs
§10 · Lifecycle hook notification targets EventBridge, SNS or SQS only EventBridge (console default), SNS or SQS (role ARN; no FIFO queues), or Lambda directly (since 28 Jul 2025; resource-based policy for AWSServiceRoleForAutoScaling), set via the CLI, CloudFormation or an SDK AWS docs
§11 · CloudWatch Logs agent (awslogs) The CloudWatch Logs agent is a valid choice for shipping EC2 logs Deprecated; it does not work on instances that require IMDSv2. Use the unified CloudWatch agent, which AWS strongly recommends in all cases. AWS docs
§11 · PutMetricData request limits 20 metrics and 40 KB per PutMetricData call Up to 1,000 metrics and 1 MB per request (gzip allowed), 150 values per Values array, 30 dimensions per metric; timestamps from 2 weeks in the past to 2 hours in the future AWS docs
§11 · High-resolution alarm periods Sub-minute alarm periods are 10 and 30 seconds only Valid periods are 10, 20 or 30 s, or any multiple of 60 s. Sub-minute periods are only useful on StorageResolution 1 metrics: on standard data the alarm often lapses into INSUFFICIENT_DATA and is still billed as a high-resolution alarm. AWS docs
§11 · Simplified automatic recovery Automatic instance recovery always requires a CloudWatch alarm with the recover action Enabled by default on supported instances; it acts on system status check failures only, never when only the instance check fails. Bare-metal instances, instances with instance store volumes, Dedicated Hosts, EFA instances and Auto Scaling group members are excluded. Alarm-based recover still works, with StatusCheckFailed_System. AWS docs
§11 · EC2 status check types EC2 has only system and instance status checks Four types: system, instance, attached EBS (StatusCheckFailed_AttachedEBS, Nitro only) and opt-in application status checks (HTTP/HTTPS probe of an application path, metric StatusCheckFailed_Application) AWS docs