Why
Bastions are internet-facing servers with SSH keys shared among a team. Session Manager gives IAM-controlled access with no inbound ports, full session logs and no keys to rotate.
1. Prerequisites on instances
- SSM Agent installed and running (pre-installed on Amazon Linux, recent Ubuntu and Windows AMIs).
- Instance profile with
AmazonSSMManagedInstanceCore. - Network path to SSM: NAT gateway, or VPC interface endpoints for
ssm,ssmmessagesandec2messagesin private subnets.
for s in ssm ssmmessages ec2messages; do
aws ec2 create-vpc-endpoint --vpc-id vpc-0123 --vpc-endpoint-type Interface \
--service-name com.amazonaws.eu-west-1.$s --subnet-ids subnet-a subnet-b \
--security-group-ids sg-endpoints --private-dns-enabled
done
aws ssm describe-instance-information --query "InstanceInformationList[].[InstanceId,PingStatus,PlatformName]" --output table
Or enable Default Host Management Configuration so instances get SSM permissions without editing every instance profile.
2. Logging and encryption
Session Manager preferences: stream session logs to CloudWatch Logs and/or S3, encrypt with a KMS key, and set an idle timeout.
3. Control who can connect
{
"Effect": "Allow",
"Action": "ssm:StartSession",
"Resource": [
"arn:aws:ec2:eu-west-1:111122223333:instance/*",
"arn:aws:ssm:eu-west-1::document/AWS-StartPortForwardingSessionToRemoteHost"
],
"Condition": { "StringEquals": { "ssm:resourceTag/team": "${aws:PrincipalTag/team}" } }
}
Tag-based conditions let teams reach only their own instances.
4. Daily use
Shell:
aws ssm start-session --target i-0abc123
SSH and SCP over Session Manager (keeps existing tooling):
# ~/.ssh/config
Host i-* mi-*
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
Port forwarding to a private RDS database through an instance:
aws ssm start-session --target i-0abc123 \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["orders.cluster-abc.eu-west-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["15432"]}'
psql -h localhost -p 15432 -U app orders
RDP to Windows: forward 3389 with AWS-StartPortForwardingSession and connect to localhost:13389, or use Fleet Manager's browser-based RDP.
5. Close the doors
- Confirm everyone has used Session Manager successfully for a couple of weeks.
- Remove inbound 22/3389 rules from all security groups:
aws ec2 describe-security-groups --filters Name=ip-permission.from-port,Values=22 \
--query "SecurityGroups[].[GroupId,GroupName]" --output table
- Terminate bastion hosts; remove their Elastic IPs and key pairs.
- Add an AWS Config rule (
restricted-ssh) to catch new open SSH rules.
Checklist
- SSM Agent and permissions on every instance; endpoints in private subnets
- Session logging to CloudWatch/S3 with KMS
- IAM policies scoped by tags
- Port forwarding documented for DB and RDP access
- Inbound 22/3389 removed; bastions terminated