AWS EC2 for beginners usually goes one of two ways: you click through the launch wizard in five minutes and get a box that works, or you spend a few more deliberate minutes and get a box you can actually put traffic on. The difference is not the launch itself. It is five choices in the wizard and four things you do on the first SSH connection. A default-wizard instance runs everything as the default user, sits behind a security group open to the world, and loses its public IP the moment you stop it. A production-ready one does not. I have run web apps on EC2 for years, and the gap between those two outcomes is maybe ten extra minutes. Here is exactly what to do, in order.
Which launch wizard choices actually matter?
The wizard throws a dozen fields at you, but only five change whether this instance survives contact with real traffic. The rest you can leave at defaults. Get these five right and you have skipped most of the mistakes I see beginners make in their first month.
- AMI: pick a current Ubuntu LTS image (24.04 LTS, or 22.04 if your stack needs it). Choose the official Canonical AMI, not a random community one. The login user is ubuntu, which matters in a moment.
- Instance type: start with t3.small, not t2.micro. The free-tier t2.micro has 1 GB of RAM and will OOM-kill your app the first time Composer or npm runs alongside it. t3.small gives you 2 GB and burstable credits that behave more predictably under load.
- Key pair: create one, download the .pem, and actually save it somewhere you will not lose it. AWS shows you the private key exactly once. Lose it and you are rebuilding the instance to get back in.
- Security group: this is the firewall. Do NOT accept the wizard default that opens SSH to 0.0.0.0/0. Allow 22 from your IP only, 80 and 443 from anywhere.
- Storage: switch the root EBS volume from gp2 to gp3. Same price tier, but gp3 gives you a guaranteed 3,000 IOPS baseline at any volume size, instead of gp2's IOPS scaling with disk size at 3 IOPS per GB. There is no reason to launch gp2 in 2026.
The security group is the one people get wrong most often. A group that allows port 22 from anywhere means every SSH brute-force bot on the internet is hammering your login within the hour. Lock it to your own IP. If your home IP is dynamic, you will re-edit this rule occasionally, which is mildly annoying and far better than the alternative.
Type Protocol Port Source
---- -------- ---- ------
SSH TCP 22 203.0.113.10/32 (your IP only)
HTTP TCP 80 0.0.0.0/0
HTTPS TCP 443 0.0.0.0/0
# Outbound: leave the default (all traffic allowed). If you later
# tighten egress and your apt installs or git clones start hanging,
# that is almost always the cause.That outbound note bites people constantly. Security groups are stateful, so inbound replies are not the issue, but if you start restricting egress, apt and git over HTTPS are the first things to break silently. I wrote up that exact failure in why your outbound TCP is blocked on EC2 because I have debugged it more than once at 2am.
How do I connect for the first time?
Once the instance shows 'running' and a status check passes, grab its public IPv4 address. The .pem you downloaded must be readable only by you or SSH will refuse to use it, so the chmod is not optional. The login user is ubuntu because we chose an Ubuntu AMI; an Amazon Linux AMI would use ec2-user instead.
# Lock down the key file (SSH refuses keys that are group/world readable)
chmod 400 ~/Downloads/my-key.pem
# Connect. 'ubuntu' is the default user on Ubuntu AMIs.
ssh -i ~/Downloads/my-key.pem ubuntu@203.0.113.42
# If you get "Permission denied (publickey)", you are almost certainly
# using the wrong username (ec2-user vs ubuntu) or the wrong key.What turns a default instance into a production-ready one?
You can now run commands with sudo on a fresh box. Four steps move it from 'works' to 'I would put a customer on this': patch it, create a non-root deploy user, raise a host firewall, and give it a stable address. None of these take long, and skipping them is what people regret later.
Patch, then stop logging in as the privileged account
Update everything first, because the AMI was baked at some point in the past and ships with known-old packages. Then create a real user for deployments. Running your app and your shell as the default ubuntu account (which has passwordless sudo) means one slip or one compromised dependency is game over. A separate deploy user with its own SSH key is the cheapest blast-radius reduction you will ever make.
# Bring the box fully up to date first
sudo apt update && sudo apt upgrade -y
# Create a non-root user for deployments and day-to-day work
sudo adduser deploy # set a password when prompted
sudo usermod -aG sudo deploy # grant sudo
# Give 'deploy' its own SSH access (do not reuse the ubuntu key blindly)
sudo mkdir -p /home/deploy/.ssh
sudo cp ~/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh
sudo chmod 600 /home/deploy/.ssh/authorized_keysRaise the host firewall and turn on automatic security updates
The security group is your network-edge firewall, but a host firewall (ufw) is a second layer that travels with the instance even if a group gets misconfigured. Allow OpenSSH before you enable ufw, or you will lock yourself out of your own box. Then turn on unattended-upgrades so security patches land without you remembering to SSH in.
# Allow SSH FIRST, or 'ufw enable' will cut your current session
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
# Automatic security updates (the package is usually preinstalled on Ubuntu)
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgradesThe fastest way to lock yourself out of a brand-new EC2 instance is to run ufw enable before you have allowed OpenSSH. Allow SSH first, every single time.
Attach an Elastic IP so the address survives a stop/start
The public IP a fresh instance gets is ephemeral. Stop the instance to resize it or save money overnight, start it again, and the IP changes, breaking every DNS record and SSH config pointing at it. An Elastic IP is a static address you allocate to your account and associate with the instance, and it stays put across stop/start cycles. The cost detail to know: since February 2024 AWS bills every public IPv4 address at about $0.005 per hour, so the auto-assigned IP on a running instance already costs the same as an Elastic IP. The Elastic IP gotcha is that you keep paying for it while the instance is stopped or while it sits unassociated, so release any Elastic IP you are not using. Do this from the console (EC2 > Elastic IPs > Allocate, then Associate) or from AWS CLI v2.
# Allocate an address; note the AllocationId it returns
aws ec2 allocate-address --domain vpc
# Associate it with your instance
aws ec2 associate-address \
--instance-id i-0abc123def4567890 \
--allocation-id eipalloc-0abc123def4567890
# When you decommission the box, release it so you stop paying for it
# aws ec2 release-address --allocation-id eipalloc-0abc123def4567890So what does production-ready actually add?
Strip it back and the production checklist over the bare wizard is short and concrete. Each item maps to a real failure I have either caused or cleaned up after.
- A non-default security group: SSH locked to your IP, not the world, so brute-force bots never reach the login prompt.
- A non-root deploy user: your app and shell do not run as a passwordless-sudo account, so one compromised dependency is not instant root.
- Automatic security updates: unattended-upgrades patches CVEs while you sleep instead of you forgetting for three months.
- An Elastic IP: the address survives stop/start, so DNS and SSH configs do not break the first time you resize the box.
- A host firewall plus gp3 storage: defence in depth at the OS level, and disk that will not throttle the moment you get real traffic.
If you are deciding whether EC2 is even the right tool versus a simpler managed box, my comparison of EC2 vs Lightsail is worth a read first, and once this single instance grows into a real stack with a load balancer and managed database, the production architecture I deploy picks up where this leaves off. If you want to cross-check any flag, the official 'Get started with Amazon EC2' tutorial in the AWS docs is the authoritative reference.
Launching an EC2 instance is genuinely a five-minute task, and AWS designed the wizard so that the easy path mostly works. The trap is that 'mostly works' and 'production-ready' look identical until the night a bot finds your open SSH port or a stop/start silently changes your IP. The ten extra minutes here are not gold-plating; they are the difference between an instance you babysit and one you can forget about. Do the five wizard choices and the four first-connection steps once, turn them into a habit, and every box you launch after this starts from a defensible baseline.

