Mastering Secure SSH Access to Amazon EC2: Beyond the Basics
Default SSH access to Amazon EC2 remains a perennial weak point in many organizations’ cloud security. Too often, teams settle for generated key pairs and public security group rules—easy, but vulnerable. Automated scans relentlessly probe port 22, authentication keys grow stale, and operational overhead balloons with each new user or environment.
Critical question: Does your current SSH setup survive a motivated attacker—or even a single misstep in key management?
Elevating EC2 SSH Practice: Practical Steps
Instance Connect: Short-Lived, Policy-Driven Access
AWS EC2 Instance Connect replaces the traditional model of distributing SSH keys. Instead, it works via IAM, granting ephemeral access for user sessions.
No need to persist user keys on disk. No long-term exposure.
Enable EC2 Instance Connect (Amazon Linux 2, Ubuntu >=18.04)
- Attach a minimally privileged IAM role:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2-instance-connect:SendSSHPublicKey" ], "Resource": "arn:aws:ec2:us-east-1:123456789012:instance/i-0123456789abcdef0" } ] }
- Restrict port 22 to only known management sources. Even with Instance Connect, a broad exposure (e.g.,
0.0.0.0/0
) defeats the control. - Connect via AWS CLI:
After key acceptance (valid for 60 seconds), initiate the SSH session:aws ec2-instance-connect send-ssh-public-key \ --region us-east-1 \ --instance-id i-0123456789abcdef0 \ --availability-zone us-east-1a \ --instance-os-user ec2-user \ --ssh-public-key file://~/.ssh/id_rsa.pub
Note: Failure to update IAM or insufficient OS-side user permissions often returns:ssh -i ~/.ssh/id_rsa ec2-user@<instance-public-ip>
Error: InvalidInstanceID.NotFound
Harden Security Groups: Port 22 Is Not a Public Resource
Leaving TCP 22 open (0.0.0.0/0
) guarantees continuous scans.
- Allow only known IPs (corporate ranges, VPN endpoints):
aws ec2 authorize-security-group-ingress \ --group-id sg-0abcd1234ef5678gh \ --protocol tcp --port 22 \ --cidr 203.0.113.42/32
- For dynamic IPs, automate updates. Not elegant, but better than leaving the aperture wide.
Gotcha: Some ISPs rotate public IPs during outages—automated scripts often miss this.
Jump Hosts and Bastions: Controlled Ingress
Deploy a bastion host for SSH access, isolating application and database workloads within private subnets. Harden this entry point:
- Enforce MFA and monitored audit logs.
- Only required users in the security group.
- Disable root login, enforce SSH key authentication, monitor with tools like
fail2ban
. - Rotate bastion AMI regularly to ensure patching.
Alternatively, eliminate the SSH entry point altogether with AWS Systems Manager—Session Manager (covered next).
AWS Systems Manager Session Manager: SSH-less, Zero Inbound
Session Manager enables admin shell sessions directly over HTTPS—removing the need for open inbound ports or even SSH keys.
- Start a session:
aws ssm start-session --target i-0123456789abcdef0
- Prerequisites:
- Instance IAM policy with
ssm:StartSession
,ssm:SendCommand
, etc. - SSM Agent >=3.0 installed (
sudo snap install amazon-ssm-agent
for Ubuntu, oryum install amazon-ssm-agent
).
- Instance IAM policy with
- All commands logged in AWS CloudTrail and optionally to S3.
Known issue: Certain AMI builds lack SSM Agent by default. Bake it into your custom AMIs or automate installation at launch.
Key Rotation & Lifecycle: Use AWS Secrets Manager
Long-lived key pairs are forgotten until they’re leaked. Instead:
- Store and version private keys in Secrets Manager—attach strict access controls.
- Automate key rotation (consider Lambda scheduled via CloudWatch Events).
- Update instance
authorized_keys
using automation (Ansible, user data, or cloud-init).
Trade-off: Bootstrapping a new key requires temporary elevation or a secure channel.
Local SSH Configuration Hygiene
Implement strict hygiene on engineering workstations.
~/.ssh/config
snippets centralize settings:Host bastion-prod HostName 54.111.222.33 User ec2-user IdentityFile ~/.ssh/jump-host.pem ForwardAgent yes IdentitiesOnly yes ProxyJump none
- Limit key reuse. One keyfile per team, host, or function. Use a passphrase and
ssh-agent
when possible.
Side note: GUI key managers (e.g., GNOME Keyring) sometimes lock up with multiple keys—prefer CLI where possible.
Multi-Hop, Port Forwarding, and Agent Forwarding
Tunneling or pivoting? Use SSH’s native jump support:
ssh -A -J ec2-user@bastion-prod ec2-user@10.12.34.5
This pipes agent credentials without transferring key files. Adds observable audit trail on the bastion. For RDS or internal web apps, combine with local port forwarding:
ssh -L 5432:internal-db:5432 ec2-user@bastion-prod
Summary
A hardened EC2 SSH setup is multi-layered: time-limited access (Instance Connect), strict network controls, SSH avoidance where possible (Session Manager), key lifecycle automation, and local discipline. Treat SSH not as a convenience, but as an attack vector—one exposed endpoint or stale key is a standing invitation.
Non-obvious tip: For critical environments, combine SSM Session Manager with VPC endpoints, air-gapping even the session traffic from the public Internet.
Questions on advanced EC2 access patterns? Recent war stories on tool migrations? Drop a note—infrastructure security evolves by sharing specifics, not just checklists.