Security Best Practices
AWS security is mostly a checklist, not a research project. The incidents that actually hurt teams are almost never exotic; they are a public S3 bucket, an over-broad IAM policy, a missing MFA, or an account with no audit trail. This page is a practical, ordered checklist of the highest-impact controls. Do these first, and you prevent the vast majority of real-world breaches before you ever reach for an advanced tool.
The unglamorous basics win. Many teams buy expensive scanning tools while leaving a bucket public and an admin user without MFA. Fix the basics first, they are free and they stop most incidents.
Secure the root user
The root user is the email address you signed up with. It can do anything, including close the account, and no policy can restrict it. Treat it like a fire-alarm key: lock it away and almost never use it.
When to use it: only for the handful of tasks that require root (closing the account, changing the support plan, some billing actions). For everything else, use IAM (Identity and Access Management) users or roles instead.
Console steps to harden root:
- Sign in as root, open the account menu (top right) → Security credentials.
- Under Multi-factor authentication (MFA), choose Assign MFA device and register a hardware key or authenticator app. MFA (Multi-Factor Authentication) means a second proof of identity beyond the password.
- Delete any root access keys if they exist (you should never have programmatic root keys).
- Set a long, unique password and store it in a password manager.
Check for root keys from the CLI:
aws iam get-account-summary --query 'SummaryMap.AccountAccessKeysPresent'
Output:
0
A 0 means no root access keys exist, which is what you want.
Use least-privilege IAM, and roles instead of keys
Least privilege means each identity gets only the permissions it needs, nothing more. Avoid attaching AdministratorAccess to humans or apps by default.
Prefer IAM roles over long-lived access keys (an access key is a permanent username/password pair for code). Roles hand out short-lived temporary credentials that rotate automatically, so a leaked credential expires on its own.
| Approach | Lifetime | Best for | Avoid when |
|---|---|---|---|
| IAM role (assumed) | Minutes (auto-rotates) | EC2, Lambda, ECS, cross-account access | Almost never avoid |
| IAM user + access key | Permanent until deleted | Legacy tooling that cannot assume roles | Anything running on AWS |
| Root credentials | Permanent | The few root-only tasks | Day-to-day work |
To give an EC2 instance (a virtual server) access to S3, attach a role, not keys. Create the role and instance profile via CLI:
aws iam create-role --role-name app-s3-reader \
--assume-role-policy-document file://trust-ec2.json
aws iam attach-role-policy --role-name app-s3-reader \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
Never paste access keys into code, environment files committed to Git, or container images. Leaked keys are the single most common cause of AWS account compromise. Use roles, and scan repos with tools like git-secrets.
Block public access on S3
Amazon S3 (Simple Storage Service) is object storage. By far the most famous AWS data leaks are S3 buckets accidentally made public. AWS provides Block Public Access (BPA), a master switch that overrides any permissive bucket policy or ACL.
When to use it: account-wide, always, unless you are deliberately hosting public content (and even then, scope it to one bucket).
Console steps:
- Open the S3 console → Block Public Access settings for this account.
- Click Edit, tick Block all public access, and Save.
CLI to enable account-wide BPA:
aws s3control put-public-access-block --account-id 111122223333 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Output:
(no output on success; exit code 0)
Encrypt at rest and in transit
Encryption at rest protects stored data; encryption in transit protects data moving over the network. Both are cheap and mostly automatic in 2026.
- At rest: S3, EBS volumes, and RDS databases all support default encryption with AWS KMS (Key Management Service). S3 encrypts new objects by default; enable EBS default encryption explicitly.
- In transit: require TLS (Transport Layer Security, the
https://lock) on load balancers and APIs. Add a bucket policy that denies non-TLS requests.
Turn on EBS default encryption (one command per region):
aws ec2 enable-ebs-encryption-by-default
Output:
{
"EbsEncryptionByDefault": true
}
Cost note: KMS charges about $1 per key per month plus a tiny per-request fee. Encryption itself adds no measurable performance or storage cost on modern instances.
Enable account-wide CloudTrail and GuardDuty
CloudTrail records every API call in your account, this is your audit log. Without it, you cannot answer “who did what, when” after an incident.
GuardDuty is a managed threat-detection service that watches logs for suspicious behavior (for example, credentials used from an unusual country).
When to use them: both, in every account, all the time. A single multi-region trail and GuardDuty are inexpensive and are the difference between detecting a breach and finding out months later.
Console steps for CloudTrail:
- Open CloudTrail → Create trail.
- Name it, choose Apply trail to all regions, pick a dedicated S3 bucket, and enable log file validation.
- Create.
CLI for both:
aws cloudtrail create-trail --name org-trail \
--s3-bucket-name my-cloudtrail-logs --is-multi-region-trail
aws cloudtrail start-logging --name org-trail
aws guardduty create-detector --enable
Output:
{
"DetectorId": "12abc34d567e8fa901bc2d34e56789f0"
}
Cost note: a basic multi-region trail’s management events are free; you pay only for the S3 storage of logs. GuardDuty is usage-based and typically runs a few dollars a month for small accounts.
Add SCP guardrails
If you use AWS Organizations (a way to manage many accounts together), Service Control Policies (SCPs) set hard limits that no one in an account can exceed, not even an admin. They are guardrails, not grants.
Common guardrails: deny disabling CloudTrail, deny leaving the organization, deny use of unapproved regions. Example SCP statement:
{
"Effect": "Deny",
"Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail"],
"Resource": "*"
}
When to use this: multi-account setups. SCPs do nothing in a standalone account, use IAM there instead.
Least-exposure security groups
A security group is a virtual firewall around an instance. The classic mistake is opening SSH (port 22) or RDP (port 3389) to 0.0.0.0/0, meaning the entire internet.
When to use a narrow rule: always. Restrict admin ports to your office IP or, better, use AWS Systems Manager Session Manager so you need no open inbound ports at all.
Replace an open SSH rule with a scoped one:
aws ec2 authorize-security-group-ingress --group-id sg-0a1b2c3d \
--protocol tcp --port 22 --cidr 203.0.113.10/32
The /32 means exactly one IP address.
Best Practices
- Lock away the root user, give it MFA, and delete any root access keys.
- Grant least privilege; prefer auto-rotating IAM roles over long-lived access keys everywhere.
- Turn on account-wide S3 Block Public Access unless you are deliberately serving public content.
- Encrypt at rest with KMS and require TLS in transit; both are cheap and mostly automatic.
- Enable a multi-region CloudTrail and GuardDuty in every account before chasing fancier tooling.
- Never expose SSH/RDP to
0.0.0.0/0; scope security groups tightly or use Session Manager. - Use SCPs as hard guardrails in multi-account organizations.