Guides/AWSAWS/AWS EC2 Explained

AWS EC2 Explained

EC2 from the ground up: instances and families, AMIs, key pairs, security groups, EBS vs instance store, user data, pricing, and the Auto Scaling pattern.


EC2 (Elastic Compute Cloud) is the oldest and most fundamental service in AWS, and it is still where a huge amount of real infrastructure runs. Strip away the marketing and EC2 is one thing: rented virtual machines. You ask AWS for a server of a certain size, running a certain operating system, and a few seconds later you have a Linux or Windows box you can SSH into and treat like any other computer. Everything else in EC2 - the instance types, the images, the disks, the firewall rules, the pricing games - exists to answer the questions that follow from "I have a virtual machine": how big, from what image, with what disk, reachable by whom, and at what cost. This guide walks that chain in order so the pieces connect instead of arriving as trivia.

What EC2 actually is

An EC2 instance is a virtual machine running on AWS-managed physical hardware in one of their data centers. AWS runs a hypervisor on a big physical server and carves it into isolated virtual machines; your instance is one slice, with its own virtual CPUs, memory, disk, and network interface. You do not see or manage the physical host, and you do not know which neighbors share it. You just get a machine.

That framing matters because it tells you what EC2 is and is not. It is raw compute you are responsible for - you pick the OS, patch it, install your software, and keep it running. It is not a managed platform that deploys your code for you (that is what services like Elastic Beanstalk, ECS, or Lambda add on top). An EC2 instance behaves like a server in a rack, except you create and destroy it with an API call in seconds instead of buying hardware and waiting weeks. That speed - and the ability to throw the machine away and make a fresh one - is the whole point, and it shapes how you should use it: treat instances as disposable and reproducible, not as pets you hand-tune and never rebuild.

Instances live inside a VPC (your private virtual network) and in a specific Availability Zone (an isolated data center within a region). Those are covered in the VPC networking guide; here just hold onto the fact that every instance has a place in a network and a physical location, both of which you choose.

Instances and instance types

When you launch an instance you pick an instance type, which is just the hardware profile: how many virtual CPUs, how much memory, what network bandwidth, and what kind of storage it is optimized for. AWS names types like m6i.large or c7g.xlarge, and the name encodes real information once you learn to read it.

Take m6i.large:

  • m is the family - the broad purpose (here, general purpose).
  • 6 is the generation - higher is newer, usually faster and cheaper per unit of work. Prefer the latest generation your region offers.
  • i (and letters like g, a, d, n) are attributes - i means Intel, a means AMD, g means AWS's own Graviton ARM chips, d means local NVMe storage, n means extra network bandwidth.
  • large is the size - the amount of CPU and memory. Sizes step up roughly doubling each time: nano, micro, small, medium, large, xlarge, 2xlarge, and on up.

So m6i.large is a 6th-gen general-purpose Intel instance of "large" size (2 vCPU, 8 GiB memory). c7g.4xlarge is a 7th-gen compute-optimized Graviton instance, much bigger.

Picking a family, roughly

You do not need to memorize the catalog. You need to match the shape of your workload to a family:

  • General purpose (t, m) - balanced CPU-to-memory ratio. The default starting point for web servers, small databases, app servers, and anything you are not sure about. The t family is burstable: it is cheap and idles at a fraction of full CPU, banking "credits" it spends on short bursts. Great for spiky, low-average workloads (dev boxes, low-traffic sites); a trap for anything that needs sustained CPU, because it throttles hard once credits run out.
  • Compute optimized (c) - more CPU per unit of memory. Reach for it when the bottleneck is raw processing: batch jobs, video encoding, high-traffic web tier, game servers, scientific computing.
  • Memory optimized (r, x) - lots of RAM per vCPU. For in-memory databases and caches (Redis, big Postgres/MySQL), large data processing, and anything that holds a lot of data in memory.

There are more specialized families (p/g for GPUs, i for high local storage IOPS, and so on), but general/compute/memory cover most decisions. The practical method: start with a general-purpose type, run the real workload, watch CPU and memory, and move to c or r only when the data shows one dimension is the constraint. Right-sizing after you have real usage data beats guessing up front, and because instances are disposable, changing type later is a stop-start, not a migration.

Also prefer Graviton (g) types when you can - AWS's ARM chips are typically cheaper and more power-efficient for the same work, and most modern Linux software runs on ARM without fuss.

AMIs: the image an instance boots from

An instance needs something to boot. That something is an AMI (Amazon Machine Image) - a template containing an operating system and, optionally, pre-installed software and configuration. When you launch an instance you pick an AMI, and the instance boots as a copy of it. The AMI answers "what is on the disk when this machine first turns on."

There are a few sources of AMIs:

  • AWS-provided - official base images like Amazon Linux 2023, Ubuntu, Debian, or Windows Server. This is where most people start: a clean OS you then configure.
  • Marketplace - vendor images with software pre-baked (a hardened OS, a database appliance, a monitoring stack).
  • Your own - you can take a running, configured instance and create an AMI from it. Now that fully-set-up state - OS, packages, your app, config - is a reusable template you can launch identically, over and over, in seconds.

That last point is the powerful one for operations. The mature pattern is to bake a golden AMI: build one image with everything installed and configured (often with a tool like Packer), then launch every instance from it. New instances come up already ready instead of configuring themselves on boot, which makes launches faster, more reliable, and identical every time. It is the same disposable-and-reproducible idea again: the AMI is the reproducible part.

An AMI is tied to a region, so if you build one in us-east-1 you copy it to other regions to use it there.

Key pairs and SSH access

To log into a Linux instance you use SSH with a key pair, not a password. A key pair is a public/private key: AWS holds the public key and installs it into the instance's authorized keys at launch; you hold the private key (a .pem file) and present it to prove who you are. No password is ever set or sent, which is far more secure.

You create a key pair once, download the private key, and keep it safe - AWS never gives you the private key again. Lose it and you lose SSH access to any instance that trusted only that key. Then you point SSH at it:

# Lock down the private key or SSH refuses to use it
chmod 400 my-key.pem

# Connect (user depends on the AMI: ec2-user for Amazon Linux,
# ubuntu for Ubuntu, admin for Debian)
ssh -i my-key.pem ec2-user@203.0.113.42

The username is set by the AMI, not chosen by you: Amazon Linux uses ec2-user, Ubuntu uses ubuntu, Debian uses admin. The address is the instance's public IP or DNS name.

Two things worth knowing early. First, key pairs are increasingly optional: AWS Systems Manager Session Manager lets you get a shell into an instance through the AWS API with no SSH port open and no key at all, controlled entirely by IAM permissions - which is the more secure, more auditable modern default for many teams. Second, being able to SSH depends on more than the key: the network has to allow it, which is the job of security groups.

Security groups: the instance firewall

A security group is a virtual firewall attached to an instance (technically to its network interface). It controls what traffic is allowed in and out. This is one of the most important EC2 concepts to get right, and it trips people up because of a few specific rules.

The key properties:

  • Allow-only. Security group rules only ever allow traffic. There are no deny rules. If no rule permits a given connection, it is blocked by default. You describe what is permitted, and everything else is implicitly denied.
  • Stateful. This is the one that saves you constant confusion. If you allow an inbound connection, the return traffic is automatically allowed back out, and vice versa - you do not write a matching rule for the response. Allow inbound HTTPS on 443 and the replies flow back without an outbound rule. (This is the opposite of network ACLs, the VPC-level firewall, which are stateless and need both directions - see the VPC guide.)
  • Default egress is open. A new security group allows all outbound traffic by default; you usually restrict inbound and leave outbound broad unless you have a reason to lock it down.
  • They reference other security groups. A rule's source can be an IP range (a CIDR block) or another security group. This is how you build clean tiers: "allow the web tier's security group to reach the database on 5432" without hard-coding any IP addresses. As instances come and go, the rule keeps working because it targets the group, not addresses.

A typical web server's inbound rules:

  • 443 (HTTPS) from 0.0.0.0/0 - open to the internet.
  • 80 (HTTP) from 0.0.0.0/0 - to redirect to HTTPS.
  • 22 (SSH) from your office IP only, 198.51.100.10/32 - never open SSH to the whole internet.

That last line is the single most common security mistake: leaving port 22 open to 0.0.0.0/0. Scanners find it within minutes and hammer it. Restrict SSH to known IPs, or better, drop the key/SSH model entirely and use Session Manager. A security group can attach to many instances, and an instance can carry several security groups (the rules add up - a connection is allowed if any group allows it).

EBS volumes vs instance store

Every instance needs disk, and EC2 gives you two fundamentally different kinds. The distinction is about what survives when the instance stops, and getting it wrong loses data.

EBS (Elastic Block Store) is durable network-attached storage. An EBS volume is a virtual disk that lives independently of any instance - it happens to be attached to yours, but it survives the instance being stopped or terminated (as long as you do not tell it to delete on termination). You can detach it and reattach it to a different instance, resize it, and snapshot it to S3 for backups. This is where anything you care about lives: the OS root disk on most modern instances, your databases, your application data. EBS is the default and the right choice almost always.

# Snapshot an EBS volume for backup
aws ec2 create-snapshot \
  --volume-id vol-0abc123 \
  --description "nightly backup"

Instance store is temporary disk that is physically attached to the host machine your instance runs on. It is fast (local NVMe on some types), but it is ephemeral: the moment the instance stops or terminates, or moves to different hardware, everything on the instance store is gone forever. There is no recovery. It is only appropriate for data you can afford to lose completely - scratch space, caches, temporary processing files, or a local replica you can rebuild.

The rule is simple: if you would be upset to lose it, it goes on EBS. Instance store is a performance tool for throwaway data, never a place for anything that matters. A classic painful lesson is putting a database on instance store, stopping the instance to save money overnight, and finding the data vanished. EBS survives stop/start; instance store does not.

Snapshots are the backup story for EBS: incremental, stored in S3, and the basis for both backups and creating volumes (or AMIs) in other regions.

User data: bootstrapping on first boot

When you launch an instance you can hand it a user data script - a shell script (or cloud-init config) that runs automatically the first time the instance boots. This is how an instance configures itself without you logging in: install packages, pull code, write config, start services.

#!/bin/bash
dnf update -y
dnf install -y nginx
systemctl enable --now nginx
echo "<h1>Hello from $(hostname)</h1>" > /usr/share/nginx/html/index.html

Pass that at launch and the instance comes up already serving a web page. User data is what makes instances reproducible from a definition rather than hand-built: paired with Auto Scaling (below), it means every new instance configures itself identically, with no human in the loop.

There is a design choice between two styles. You can do all the setup in user data at boot (simple, but slower launches and a chance for a package install to fail mid-boot), or you can bake a golden AMI with everything pre-installed and use user data only for the last few instance-specific touches (faster, more reliable launches). For anything at scale, the golden-AMI-plus-thin-user-data approach wins, for the same reason baking beats configuring on the fly everywhere else.

Pricing models: on-demand, reserved, and spot

The same instance can cost wildly different amounts depending on how you buy it. There are three models that matter, and choosing well is one of the biggest levers on an AWS bill.

  • On-demand - pay by the second (or hour) with no commitment. The most expensive per hour, but zero lock-in: start and stop whenever, pay only for what you run. This is the default and the right choice for unpredictable, spiky, or short-lived workloads, and for anything new where you do not yet know the steady-state usage.
  • Reserved Instances / Savings Plans - commit to a level of usage for 1 or 3 years and get a large discount (often 40 to 70 percent off on-demand) in return. Savings Plans are the modern, more flexible form: you commit to spending a certain dollars-per-hour on compute and the discount applies across instance types and regions, rather than locking to one exact instance. This is for your steady-state baseline - the capacity you know you will run 24/7 for the next year (the always-on production fleet). The trade is commitment for a big discount.
  • Spot - bid on AWS's spare capacity for up to ~90 percent off on-demand, with one catch: AWS can reclaim the instance with two minutes' notice when it needs the capacity back. Spot is enormous savings for fault-tolerant, interruptible work - batch processing, CI runners, big data jobs, stateless workers behind a queue, anything that can lose a node and simply retry. Never put something that cannot tolerate sudden termination on pure spot.

The mature setup blends all three: cover the always-on baseline with a Savings Plan, absorb variable demand with on-demand, and run interruptible bulk work on spot. That combination often cuts a compute bill by more than half versus all-on-demand, with no change to the workload itself.

The resilient pattern: Auto Scaling + load balancer

A single instance is a single point of failure - if it dies, or the whole Availability Zone has trouble, you are down. The standard production answer combines two services with EC2, and it is worth knowing as a shape even before you build one.

An Auto Scaling Group (ASG) manages a fleet of identical instances instead of one. You give it a launch template (which AMI, which instance type, which security groups, what user data) and a desired count, and the ASG keeps exactly that many healthy instances running across multiple Availability Zones. If an instance fails a health check, the ASG terminates it and launches a replacement automatically - self-healing, the same reconcile-to-desired-state idea you see across cloud infrastructure. It can also scale on demand: add instances when CPU or request count climbs, remove them when load falls, so you pay for capacity that matches traffic.

A load balancer (usually an Application Load Balancer for HTTP) sits in front of the group as a single stable entry point. It spreads incoming requests across all the healthy instances and stops sending traffic to any instance that fails its health check. So the two work together: the load balancer distributes traffic and routes around sick instances, and the ASG replaces sick instances and adjusts the count.

Put together, the pattern is: a load balancer in front, an Auto Scaling Group of instances (launched from a golden AMI, across at least two AZs) behind it. That gives you high availability (lose an instance or a whole AZ and the rest carry on), elasticity (capacity tracks load), and self-healing (dead instances get replaced), with no single machine to babysit. It is the default way to run a resilient service on EC2, and it is why treating instances as disposable and reproducible - AMIs, user data, no hand-tuning - pays off: the ASG is constantly creating and destroying them for you.

The shape of it

EC2 is rented virtual machines, and every other concept answers a question that follows from that. Instance type is how big and what shape (general/compute/memory - start general, right-size on real data, prefer Graviton). AMI is what boots (start from an AWS base, graduate to a golden image). Key pairs are how you log in (or skip them with Session Manager). Security groups are the stateful, allow-only firewall around the instance - lock down SSH, reference other groups to build tiers. EBS is durable disk that survives the instance; instance store is fast, throwaway disk that does not - if it matters, it goes on EBS. User data bootstraps an instance on first boot so it configures itself. Pricing is on-demand for the unpredictable, Savings Plans for the steady baseline, spot for the interruptible. And the production shape is an Auto Scaling Group behind a load balancer across multiple AZs, which turns disposable instances into a resilient, self-healing service. Get comfortable with those, connect them to VPC networking and IAM, and the rest of EC2 is detail on a frame you already understand.