Mastering Secure SSH Access to EC2 Instances: Beyond the Basics
SSH remains the default entry point to most AWS EC2 Linux workloads. That makes SSH not just a convenience, but the primary security boundary between public networks and your infrastructure. Far too often, security on EC2 comes down to “just set a key pair and open port 22,” which exposes operations to avoidable risk—especially at scale or under active threat models.
This guide covers how experienced operators lock down SSH, blending AWS-native controls, modern Linux best practices, and operational patterns that survive real-world audits.
Threat Model: Why SSH to EC2 is a Priority Target
Default EC2 SSH access patterns are a case study in “works by default, insecure by default.” Direct exposure of port 22 to the internet or using weak keys can quickly turn one mistake into an incident. Even a small lapse in credential management (stale key, old IAM account, open security group) is enough.
Key objectives:
Objective | Attack Prevented |
---|---|
Narrow SSH ingress (security group) | Opportunistic scanning/bruteforce |
Key-only auth, no passwords | Credential stuffing |
Disable root login | Privilege escalation |
Session logging | Silent breaches |
Rotate/revoke access efficiently | Insider threats, leaky keys |
1. Security Groups & Network Access Control
There’s no reason to allow 0.0.0.0/0
on port 22. Either restrict SSH access to office/VPN CIDRs or, better, use temporary access per-session via AWS SSM Session Manager.
Example: Harden a security group from day zero.
aws ec2 revoke-security-group-ingress \
--group-id sg-xxxxxxx \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxxxx \
--protocol tcp \
--port 22 \
--cidr 203.0.113.42/32
Note: That /32
locks access to a single IP. With remote teams, control ingress via an IPsec VPN or AWS VPN endpoint.
Side note: Some users proxy SSH through AWS Client VPN or OpenVPN bastions; trade-off is added latency and operational overhead.
2. Bastion Host Pattern (or skip SSH, use SSM)
Bastions (jump hosts) box SSH entry to a hardened EC2 instance, usually with extra monitoring and locked-down IAM access. All internal EC2 nodes only allow SSH from the bastion’s private IP.
Diagram:
[User Workstation] --> [Bastion:22] --> [Private EC2:22]
^
SSH keys, MFA, IPS, Logging
Alternatives (preferred):
AWS Systems Manager Session Manager. Removes the SSH port surface area entirely. SSM can use IAM, audit all sessions, and works over HTTPS, sidestepping outbound firewall rules.
Gotcha: SSM needs the AmazonSSMManagedInstanceCore
role on targets, plus agent installed and running; otherwise, fallback to bastion pattern.
3. SSH Key Management: Strength, Storage, and Rotation
- Disable password logins post-provisioning.
- Use minimum 2048-bit RSA or Ed25519 keys (
ssh-keygen -t ed25519 -a 100 …
). - Store private keys in secure storage: YubiKey (PKCS11), HashiCorp Vault, or encrypted disk.
- Avoid reusing keys across environments; track key lifetimes.
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/aws-ec2-prod
Inject public keys using launch templates, or push to ~/.ssh/authorized_keys
via user data (cloud-init
).
Disable password authentication and root login:
# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
ChallengeResponseAuthentication no
sudo systemctl reload sshd # Or 'restart' if necessary
Practical issue: AWS EC2 injects ssh keys only for the default user (ec2-user
, ubuntu
, etc). Custom AMIs may behave differently.
4. Harden the SSH Daemon
Non-defaults that actually reduce attack viability:
Port 2222
or randomized: Obscures but does not protect; only marginally helpful under high scan noise.AllowUsers ec2-user devopsadmin
- restricts accounts.LoginGraceTime 20
,MaxAuthTries 3
- slows brute force.PermitRootLogin no
(already set above).AllowTcpForwarding no
;X11Forwarding no
unless required.LogLevel VERBOSE
- more actionable login logs.
Config example:
# /etc/ssh/sshd_config
Port 2222
AllowUsers ec2-user devopsuser
LoginGraceTime 20
MaxAuthTries 3
PermitRootLogin no
LogLevel VERBOSE
AllowTcpForwarding no
X11Forwarding no
Known issue: Changing SSH port regularly triggers alert fatigue (“why can’t I log in?”). Standardize requirements at team level.
5. SSH Certificate-Based Authentication (for scale or high security)
Managing piles of SSH keys across hundreds of devs and CI/CD systems quickly gets out of hand. Solution: short-lived, signed SSH certificates backed by a trusted CA.
Workflow:
- Create an SSH CA key; keep private portion offline.
- Developers generate their own SSH keys; org operators sign them (via
ssh-keygen -s ...
) with expiration and role info. - Place CA public key on all EC2
sshd_config
:
TrustedUserCAKeys /etc/ssh/ca_user.pub
- Only signed keys are accepted—grant, rotate, and revoke via cert lifecycle, not file distribution.
Example:
ssh-keygen -f ca_user -C "Org SSH CA"
ssh-keygen -s ca_user -I ops1-jdoe -n ec2-user -V +1d jdoe.pub
- Rotate certificates on schedule (e.g., 24h).
- Rapid revocation: Remove CA pubkey, or expire user cert.
Non-obvious pain: CA key management is critical. If it leaks, anyone can issue certs.
6. Multi-Factor SSH Authentication
SSH with MFA is feasible but painful in heterogeneous Linux environments. Typical implementation: PAM Google Authenticator, Duo, or hardware tokens (Yubikey in OTP mode).
Install the google-authenticator
PAM module (tested on Amazon Linux 2):
sudo yum install -y google-authenticator
sudo -u ec2-user google-authenticator
Edit /etc/pam.d/sshd
:
auth required pam_google_authenticator.so nullok
Adjust /etc/ssh/sshd_config
:
ChallengeResponseAuthentication yes
Not perfect: Many SSH clients cache agent responses, bypassing challenge step unless configured properly. Test thoroughly, especially for automation/batch jobs.
7. Logging, Monitoring, and Audit
- Set
LogLevel VERBOSE
in sshd to log key identity and attempted logins. - Forward auth logs
/var/log/secure
(CentOS/AL2) or/var/log/auth.log
(Ubuntu) to CloudWatch, ELK, or SIEM for centralized analysis. - Use fail2ban or similar tools for basic intrusion response:
sudo yum install -y fail2ban
sudo systemctl enable --now fail2ban
Default config blocks common brute-force vectors. Tune jail parameters as needed.
Cloud-native note: Use AWS CloudTrail alongside SSM to log session connections for all managed nodes.
8. Automating Secure SSH on Provision: User Data Example
Automate SSH hardening at boot with user-data. Useful for short-lived or spot instances:
#!/bin/bash
# Only accept key auth, no root login.
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
echo 'PermitRootLogin no' >> /etc/ssh/sshd_config
# Insert deployer key (rotate as needed)
install -d -m 700 /home/ec2-user/.ssh
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...' > /home/ec2-user/.ssh/authorized_keys
chown -R ec2-user:ec2-user /home/ec2-user/.ssh
chmod 600 /home/ec2-user/.ssh/authorized_keys
systemctl reload sshd
Note: For more robust automation, move to Ansible, SSM RunCommand, or bake configs into AMIs.
Securing SSH into EC2 instances means prioritizing defense in depth—not just keeping port 22 closed, but also hardening at the daemon, network, identity, and audit layers. Adopt key/cert rotation and disable passwords on day one. SSM Session Manager is mature enough to replace most direct SSH use cases and simplifies controls and compliance.
Not every pattern will fit every environment, but even partial adoption significantly reduces risk.
For Terraform or CloudFormation templates automating these patterns, review the AWS examples—or refactor for your CI/CD context.
Gotchas discovered late are expensive. Assume every open port or stale key will be exploited—eventually, one will.