AWS Systems Manager Session Manager: Bastion-less EC2 Access

Quick answer
No bastion host, no open port 22, no SSH key to lose or rotate. Session Manager gives you a shell on an EC2 instance through the AWS API, with every session optionally logged to S3 or CloudWatch. Here's the IAM and networking setup, and the port-forwarding trick that replaces an SSH bastion entirely.
- What It Actually Requires
- Starting a Session
- Session Logging
- Port Forwarding: Replacing the SSH Bastion Pattern Entirely
- Scoping Access with IAM
7 min read · Cloud Engineering
The classic pattern — a bastion host with port 22 open, SSH keys distributed to everyone who needs access, a jump box that's itself an attack surface — has a direct replacement built into AWS: Session Manager, part of AWS Systems Manager. It gives you a shell on an EC2 instance through the AWS API instead of a network connection to the instance's SSH port, which means the instance needs no inbound ports open at all, not even from a bastion.
If you've hit "Session Manager as a fallback when SSH isn't an option" in the EC2-unreachable troubleshooting guide, this is the same tool covered as a first-class design choice rather than a rescue path.
What It Actually Requires
Three things have to be true for a session to start:
1. SSM Agent running on the instance. It ships preinstalled on current Amazon Linux 2, Amazon Linux 2023, and Ubuntu AMIs published by Canonical for AWS — for other AMIs or older base images, it needs installing manually. Check with:
sudo systemctl status amazon-ssm-agent2. An IAM instance profile with the right managed policy. The instance's role needs AmazonSSMManagedInstanceCore attached — this grants the agent permission to poll for commands and open sessions, nothing more:
aws iam attach-role-policy \
--role-name my-ec2-role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore3. Network reachability to the SSM service endpoints. For instances with a route to the internet (a public subnet, or a private subnet with a NAT gateway), this works out of the box. For fully private subnets with no NAT, you need VPC interface endpoints for three services:
1# Three endpoints required for Session Manager in a fully private subnet
2for service in ssm ssmmessages ec2messages; do
3 aws ec2 create-vpc-endpoint \
4 --vpc-id vpc-0123456789abcdef0 \
5 --service-name com.amazonaws.us-east-1.$service \
6 --vpc-endpoint-type Interface \
7 --subnet-ids subnet-0123456789abcdef0 \
8 --security-group-ids sg-0123456789abcdef0
9doneEach does a distinct job: ssm handles the core Systems Manager API calls that initiate a session, ec2messages is how the SSM Agent receives commands from the service, and ssmmessages carries the actual interactive session traffic once one starts. All three need a security group allowing inbound HTTPS (443) from the instance's subnet, and Private DNS enabled on the endpoints so the SSM Agent resolves the standard AWS hostnames to the private endpoint IPs instead of the public ones.
Starting a Session
From the CLI, once the CLI's Session Manager plugin is installed:
aws ssm start-session --target i-0123456789abcdef0Or from the console: EC2 → select the instance → Connect → Session Manager tab → Connect. Either way, you land in a shell as ssm-user (Linux) with no SSH client, no key pair, and no port 22 involved anywhere in the connection.
Session Logging
Because every session goes through the Systems Manager API rather than a direct network connection, AWS can log the session itself — not just that a connection happened, but the actual commands and output — to S3, CloudWatch Logs, or both:
{
"s3BucketName": "session-logs-production",
"s3KeyPrefix": "ssm-sessions/",
"cloudWatchLogGroupName": "/aws/ssm/sessions",
"cloudWatchEncryptionEnabled": true
}Configure this once under Systems Manager → Session Manager → Preferences, and it applies to every session account-wide. This is the concrete compliance win over ad hoc SSH: there's no way to get equivalent command-level audit logging from raw SSH access without additional tooling (a bastion with script recording, or an SSH proxy that intercepts and logs), and it's on by default here once configured rather than something engineers have to remember to enable per-session.
One exception: this command-level logging applies to interactive shell sessions only. Port-forwarding sessions and the SSH-over-Session-Manager pattern below carry encrypted tunnel traffic that Session Manager can't inspect, so neither shows up in the session log beyond the fact that a connection happened — AWS states this explicitly in its docs. If audit trail is the reason you're adopting Session Manager, that's a real gap to know about before you rely on port forwarding for database access.
AWS Cost & Architecture Review Checklist
The questions we ask in a paid AWS review — rightsizing, storage classes, network egress, and the usual five-figure surprises. Plain Markdown.
Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.
Port Forwarding: Replacing the SSH Bastion Pattern Entirely
The classic reason for a bastion isn't just shell access — it's also the jump-through for reaching things that only have a private IP, like an RDS instance or an internal admin UI. Session Manager's port-forwarding documents replace this too, without ever opening a port on anything:
# Forward local port 8888 to port 22 on the target instance itself
aws ssm start-session \
--target i-0123456789abcdef0 \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["22"],"localPortNumber":["8888"]}'To reach a different host that the instance can route to — the real bastion use case, like a private RDS endpoint the EC2 instance sits in the same subnet as — use the remote-host variant:
aws ssm start-session \
--target i-0123456789abcdef0 \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["payments-db.abc123.us-east-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["5432"]}'psql -h localhost -p 5432 now reaches the RDS instance through the tunnel, exactly like the old "SSH into the bastion with -L 5432:payments-db...:5432" pattern — but the instance you're tunneling through only ever needed the SSM Agent and IAM permissions, never an open SSH port for this to work.
Scoping Access with IAM
Session Manager access is an IAM permission, which means it can be scoped far more precisely than "has the SSH key or doesn't." Restrict ssm:StartSession to instances carrying a specific tag:
1{
2 "Version": "2012-10-17",
3 "Statement": [
4 {
5 "Effect": "Allow",
6 "Action": "ssm:StartSession",
7 "Resource": "arn:aws:ec2:us-east-1:*:instance/*",
8 "Condition": {
9 "StringEquals": {
10 "ssm:resourceTag/Environment": "staging"
11 }
12 }
13 }
14 ]
15}This engineer can open a session against anything tagged Environment: staging, and nothing else — no separate SSH key distribution, no bastion security group to manage, and revoking access is an IAM change that takes effect immediately, not a key rotation across every instance someone had access to.
Frequently Asked Questions
Does Session Manager work on Windows instances too?
Yes — the same aws ssm start-session command opens a PowerShell session on Windows instances with the SSM Agent installed (bundled by default on current Windows Server AMIs), with the same IAM and networking requirements as Linux.
Can I still use my own SSH key with Session Manager?
Yes, and it's a common hybrid pattern: aws ssm start-session --document-name AWS-StartSSHSession tunnels standard SSH traffic through Session Manager rather than opening port 22 directly, so tools that expect a real SSH connection (like scp, or IDE remote-development plugins) keep working while the network path still goes through the SSM API rather than a direct route to port 22.
What happens to a session if the instance loses connectivity to the SSM endpoints mid-session?
The session disconnects — there's no queued/offline mode. This is a reasonable trade-off: the same connectivity a session needs to start is what session logging and command relay depend on throughout, so a lost session cleanly means lost connectivity, not a silent gap in the audit trail.
Is there a cost to using Session Manager?
No additional charge for the Session Manager capability itself. Costs are the usual suspects if you add them: VPC interface endpoints bill per-endpoint-hour plus data processed, and CloudWatch Logs/S3 storage for session logs bill at their normal rates if you enable logging.
For the broader diagnostic flow when an EC2 instance is unreachable via SSH (including Session Manager as the fallback), see Fix AWS EC2 Instance Unreachable: Can't SSH In.
Standardizing bastion-less access across an AWS account, or auditing who can reach what? Talk to us at Coding Protocols — we help platform teams replace SSH sprawl with IAM-scoped access that's actually auditable.
Official References
- AWS Systems Manager Session Manager documentation — setup, logging, and port forwarding
- Setting up VPC endpoints for Systems Manager — private-subnet connectivity requirements
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


