Private EC2 Shell Access with Session Manager: No Bastion Hosts, No Inbound Ports
Replace bastion hosts with AWS Systems Manager Session Manager for private EC2 shell access. Learn the minimal IAM and network design, trust boundaries, audit checks, and failure modes.
15 Dec 2025, 07:57 UTC

The problem with bastion-host access
Standing up a bastion host means maintaining a public-facing instance, rotating SSH keys, and leaving port 22 open to the internet or a VPN range. Each of those is a standing risk. AWS Systems Manager Session Manager takes a different path: it proxies an interactive shell through the Systems Manager service using the AWS API. The instance never accepts an inbound connection. Instead, the SSM Agent on the instance maintains an outbound WebSocket channel to the service, and the operator’s CLI or console attaches to that channel after IAM authorization.
Requirements for private-only administrative access
- Interactive shell access to instances with no public IP address
- No inbound security group rules on port 22 or any management port
- Centralized audit trail of who connected, when, and what commands ran
- IAM-based authorization rather than shared SSH keys
- Session output retained for compliance review
Smallest suitable design
The minimal design has three parts: an instance profile, an operator IAM policy, and session logging preferences.
Attach the AWS managed policy AmazonSSMManagedInstanceCore to the instance profile. This allows the SSM Agent to communicate with the Systems Manager service.
The operator needs permission to start a session against a specific instance and the session document that defines the shell profile. The following policy grants that access. Replace us-east-1, 111122223333, and i-1234567890abcdef0 with your region, account ID, and target instance ID.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSessionManagerShell",
"Effect": "Allow",
"Action": [
"ssm:StartSession",
"ssm:TerminateSession",
"ssm:ResumeSession"
],
"Resource": [
"arn:aws:ec2:us-east-1:111122223333:instance/i-1234567890abcdef0",
"arn:aws:ssm:us-east-1:111122223333:document/SSM-SessionManagerRunShell"
]
}
]
}
Start the session from an operator workstation or AWS CloudShell using credentials that carry the policy above:
aws ssm start-session --target i-1234567890abcdef0 --region us-east-1
Expected check: an interactive shell prompt appears. If the agent is offline or IAM denies the request, the command fails with a target connectivity error or an access-denied message.
Instance-side prerequisites
On Amazon Linux 2 and Amazon Linux 2023, the SSM Agent is typically pre-installed. On Ubuntu, RHEL, or CentOS, install it from the Systems Manager package. Verify that it is running by executing the following command on the instance itself; it requires sudo.
sudo systemctl status amazon-ssm-agent
Expected check: the output shows active (running). If the service is inactive or failed, sessions cannot start.
Trust and data boundaries
The instance must reach Systems Manager endpoints over outbound HTTPS (port 443). No inbound security group rules are required. The operator is authenticated by IAM, so there is no SSH key to distribute or rotate.
When session logging is enabled, the instance streams shell output to Amazon CloudWatch Logs or an Amazon S3 bucket. If a KMS key encrypts those logs, the key policy must allow the logging principal to call kms:Decrypt and kms:GenerateDataKey. Session Manager does not manage OS-level users; by default the session opens as ssm-user. You can specify a different run-as user through session parameters, but that user must already exist on the instance.
Operational checks
| Check | Where to run | Expected result |
|---|---|---|
| Agent status | On the instance: sudo systemctl status amazon-ssm-agent | active (running) |
| Managed node status | Systems Manager console → Fleet Manager | Instance shows Online |
| Session start | Operator CLI: aws ssm start-session --target i-... | Shell prompt appears |
| Audit trail | CloudTrail event history | StartSession event with user identity |
| Session output | CloudWatch Logs group or S3 bucket | Log stream contains shell activity |
Failure modes that block sessions
- Outbound connectivity loss: if the subnet has no NAT gateway and no interface VPC endpoints, the agent cannot reach the service.
- Missing IAM permissions: the instance role lacks
AmazonSSMManagedInstanceCore, or the operator lacksssm:StartSession. - Stopped agent: the service is inactive after a reboot or failed update.
- KMS key policy denial: logging is configured, but the key policy does not allow the logging principal.
- Misconfigured logging: no CloudWatch Logs group or S3 bucket is set in Session Manager preferences, so session data is not retained.
Conditions that change the design
- Port forwarding to internal services: if you need to reach an RDS database or internal HTTP service through the session, use the
AWS-PortForwardingsession document and grant the operator permission for that document. - Cross-account access: if instances live in a different AWS account, the operator needs a role in the instance account with
sts:AssumeRole, and the instance account must trust the operator account. - Hybrid on-premises nodes: non-EC2 machines require a hybrid activation and a different IAM service role, not an EC2 instance profile.
- Fully isolated VPCs: without internet egress, create interface VPC endpoints for
ssm,ssmmessages, andec2messagesso the agent can reach the service privately.
Limitations and how to verify the result
Session Manager is not a replacement for OS user management; it assumes the target user already exists. It also does not provide a graphical desktop or file-transfer protocol by default, though you can enable those through session preferences. To verify the design works, start a test session against a non-production instance, confirm the shell opens, then check CloudTrail for the StartSession event and confirm that session output appears in the configured CloudWatch Logs group or S3 bucket. If the log destination is empty, revisit the Session Manager preferences and the resource policies on the log group or bucket.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.