Connecting with Session Manager (No SSH)
Session Manager is a feature of AWS Systems Manager (often shortened to SSM, a service for managing your servers at scale) that gives you a shell on an EC2 (Elastic Compute Cloud) instance without SSH (Secure Shell, the classic remote-login protocol). There are no SSH keys to manage, no port 22 to open, and no bastion host (a jump server you SSH through to reach private machines). Instead of you connecting in to the instance, a small program on the instance connects out to AWS, and your shell rides back through that connection. Every session is logged, which is why security teams love it.
How Session Manager works
A tiny program called the SSM Agent runs on your instance. It opens an outbound HTTPS connection (encrypted web traffic on port 443) to the AWS Systems Manager service and waits for instructions. When you start a session, AWS routes your terminal through that already-open connection.
This flips the normal security model. With SSH you must open an inbound port and protect it with keys. With Session Manager nothing needs to be open inbound at all — the instance reaches out, AWS authorizes you with IAM (Identity and Access Management, the AWS permissions system), and the shell tunnels back over TLS.
SSH vs bastion host vs Session Manager
| Approach | Inbound port needed | Key management | Audit logging | Best for |
|---|---|---|---|---|
| Direct SSH | Yes (port 22) | You manage key pairs | Manual / none | Quick one-off access on public instances |
| Bastion host | Yes (on the bastion) | Keys on every hop | Manual | Legacy private-subnet access |
| Session Manager | No inbound at all | None (IAM-based) | Automatic | Modern, audited access to public or private instances |
Tip: Because Session Manager needs no open ports, a common security baseline is to remove the inbound SSH rule (port 22) from your security groups entirely and rely on Session Manager for shell access.
When to use this (and when not)
Use Session Manager when you want auditable, key-free access — especially for private instances, for teams where managing SSH keys is painful, or when compliance requires a record of who ran what.
It is less ideal when you need to copy large files interactively, run GUI tools, or use SSH-specific tooling. (Session Manager can tunnel SSH and forward ports, but plain SSH is simpler for those niche cases.) For routine shell work, Session Manager is the modern default.
Prerequisites
Three things must be true or the connection silently fails:
- The SSM Agent must be installed and running. It is preinstalled on Amazon Linux 2/2023, recent Ubuntu, and Windows Server AMIs (Amazon Machine Images, the templates instances boot from). On other images you install it yourself.
- The instance needs an IAM role (a set of permissions attached to the instance) that allows it to talk to SSM. AWS provides a managed policy called
AmazonSSMManagedInstanceCorefor exactly this. - The instance needs a network path to the SSM endpoints. A public instance reaches them over its internet route. A private instance needs either a NAT gateway (a device that lets private machines make outbound internet calls) or VPC endpoints (private doorways into AWS services that skip the internet entirely).
Attaching the IAM role
Console steps:
- Open the IAM console and choose Roles > Create role.
- Trusted entity type: AWS service; use case: EC2. Click Next.
- Search for and select AmazonSSMManagedInstanceCore. Click Next.
- Name it
EC2-SSM-Roleand click Create role. - Go to the EC2 console, select your instance, then Actions > Security > Modify IAM role, and attach
EC2-SSM-Role.
CLI equivalent:
# Create the role with a trust policy that lets EC2 assume it
aws iam create-role \
--role-name EC2-SSM-Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name EC2-SSM-Role \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# Wrap the role in an instance profile and attach it to the instance
aws iam create-instance-profile --instance-profile-name EC2-SSM-Role
aws iam add-role-to-instance-profile \
--instance-profile-name EC2-SSM-Role --role-name EC2-SSM-Role
aws ec2 associate-iam-instance-profile \
--instance-id i-0a1b2c3d4e5f \
--iam-instance-profile Name=EC2-SSM-Role
After a minute or two, confirm the instance is registered with SSM:
aws ssm describe-instance-information \
--query "InstanceInformationList[].{Id:InstanceId,Ping:PingStatus,Agent:AgentVersion}"
Output:
[
{
"Id": "i-0a1b2c3d4e5f",
"Ping": "Online",
"Agent": "3.3.1142.0"
}
]
If your instance does not appear here, it is almost always the IAM role or the network path — see the gotcha below.
Connecting
From the Console
- Open the EC2 console and select your instance.
- Click Connect at the top.
- Choose the Session Manager tab.
- Click Connect. A browser-based terminal opens in a new tab.
From the CLI
You need the Session Manager plugin installed alongside AWS CLI v2 (it is a separate small download from AWS). Then:
aws ssm start-session --target i-0a1b2c3d4e5f
Output:
Starting session with SessionId: jsingh-0a1b2c3d4e5f6a7b8
sh-5.2$ whoami
ssm-user
sh-5.2$
You land as the ssm-user account. To end the session, type exit.
You can also forward a local port to a service on the instance — handy for reaching a database without opening it to the internet:
aws ssm start-session \
--target i-0a1b2c3d4e5f \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["5432"],"localPortNumber":["5432"]}'
The big gotcha: private instances with no network path
The single most common reason Session Manager “doesn’t connect” is that a private instance has no route to the SSM endpoints. The SSM Agent is running and the IAM role is attached, but the agent’s outbound HTTPS calls go nowhere, so the instance never shows up in describe-instance-information.
You have two fixes:
- Add a NAT gateway so the private subnet can make outbound calls. Simple, but a NAT gateway costs roughly $0.045/hour (about $32/month) plus per-GB data processing in most regions.
- Add VPC interface endpoints for the three services Session Manager uses:
ssm,ssmmessages, andec2messages. Traffic stays inside the AWS network — more secure and often cheaper than NAT if SSM is the only thing you need outbound.
# Terraform: the three endpoints Session Manager requires on a private subnet
locals {
ssm_services = ["ssm", "ssmmessages", "ec2messages"]
}
resource "aws_vpc_endpoint" "ssm" {
for_each = toset(local.ssm_services)
vpc_id = "vpc-0a1b2c3d"
service_name = "com.amazonaws.us-east-1.${each.value}"
vpc_endpoint_type = "Interface"
subnet_ids = ["subnet-0a1b2c3d"]
security_group_ids = ["sg-0a1b2c3d"] # must allow inbound 443 from the instance
private_dns_enabled = true
}
Warning: The security group on your interface endpoints must allow inbound HTTPS (port 443) from the instances. If 443 is blocked, the agent connects nowhere and the instance stays “Connection lost” forever.
Best Practices
- Attach
AmazonSSMManagedInstanceCorevia an instance role and avoid storing SSH keys altogether. - Remove inbound port 22 from security groups once Session Manager works — fewer open ports, smaller attack surface.
- On private subnets, prefer VPC interface endpoints over a NAT gateway when SSM is your only outbound need.
- Turn on session logging to Amazon S3 or CloudWatch Logs so every command is recorded for audit.
- Use IAM policies to restrict who can start sessions and which instances they can reach.
- Keep the SSM Agent up to date; older agents lack features and fixes.