What a Bastion Server Does (and Why Auditors Dislike It)
A bastion host is the single reinforced door on an otherwise windowless concrete building. Its job is to give administrators a controlled way to reach servers buried inside private subnets—machines with no public IP addresses and no direct route from the internet. You SSH or RDP into the bastion, and from there you jump to your database server, your application host, or anything else on the internal network. In AWS terms, the bastion lives in a public subnet with an internet gateway, while everything it protects sits in private subnets that only accept traffic from the bastion’s security group.
The problem is that this reinforced door still has to be visible from the street. A traditional bastion requires an inbound SSH rule on port 22 (or RDP on 3389 for Windows), which means you’ve permanently punched a hole in your perimeter that any scanner on the internet can find. According to Palo Alto Networks’ Unit 42 threat research, exposed RDP and SSH services remain among the top vectors for brute-force attacks, with a newly provisioned public-facing instance attracting login attempts within minutes of going live. You cannot hide a traditional bastion, and you cannot close the port it depends on.
Auditors and security teams have soured on this model for three reasons. First, the long-lived SSH key pairs that most teams rely on become a nightmare to rotate. When someone leaves the organization, revoking their access means pulling their public key from every bastion’s authorized_keys file or—more commonly—hoping nobody forgets to do it. Second, the bastion’s operating system itself becomes a liability. Every unpatched vulnerability in OpenSSH or the underlying AMI is a potential entry point sitting directly on the public internet. Third, and most damning during a SOC 2 audit, the native SSH daemon logs show that a key was used, but they rarely map cleanly to which human used it—especially when teams share keys, which they inevitably do under pressure. That gap between “someone authenticated” and “we know exactly who ran what command” turns a compliance checkbox into a finding.
The Real Risk Isn’t the Bastion—It’s the Key Management Sprawl
Ask any security engineer who’s been through a SOC 2 audit what fails the access-control check, and it’s rarely the bastion host itself. It’s the drawer of unmanaged SSH keys. One former engineer’s private key still sitting on a decommissioned laptop. A shared bastion-prod.pem file floating through Slack DMs from six months ago. These aren’t edge cases—they’re the default state of a team that outgrew manual credential management two hires ago.
The core problem is persistence. A traditional bastion requires each developer to hold a long-lived private key, often stored as a plaintext file on their workstation with chmod 400 as the only defense. If that laptop gets left in a rideshare, the key walks away with it. When someone leaves the company, you’re stuck rotating keys across every instance they could reach—and in practice, most teams skip the rotation and hope for the best. That gap between offboarding and key revocation is exactly what auditors flag.
Then there’s the identity problem. The bastion authenticates a key, not a person. Without federation to your corporate identity provider, you lose MFA enforcement, single sign-on, and the ability to kill access in one place. A session logged as ec2-user from an IP address tells you nothing about who ran a command—and that missing link between a shell session and a specific IAM user or Okta identity fails access-review requirements outright. The fix isn’t a tighter security group rule. It’s eliminating the keys entirely.
Session Manager: The Portless Approach That Eliminates the Bastion
If opening port 22 feels like leaving your front door unlocked, Session Manager is the deadbolt that makes the door disappear. Instead of SSH’ing through a bastion, you connect via an encrypted HTTPS tunnel brokered by the Systems Manager (SSM) service—no inbound security group rules, no public IPs, and no keys to lose.
The architecture is simple: a lightweight SSM Agent installed on your EC2 instances initiates an outbound connection to AWS’s endpoint. That means your instances can sit in private subnets without any exposure to the internet. When you run aws ssm start-session from your CLI or click “Connect” in the console, AWS’s IAM engine evaluates your permissions and—if authorized—patches you through. Access is gated purely by IAM policies, which you can further harden by requiring MFA. To revoke a former employee’s access, you delete their IAM user or role.
Auditability is baked in. Every StartSession API call is logged in AWS CloudTrail, giving you an immutable record of who connected to what and when. You can pipe session output to S3 or CloudWatch Logs, and enable full session recording for forensic replay—a capability that satisfies SOC 2 and PCI DSS examiners who previously bristled at the “anonymous root user” problem of shared bastion keys.
To make this work, you need three things: the SSM Agent running on your instances (pre-installed on Amazon Linux 2 and most recent AMIs), an instance profile attached to the EC2 with the AmazonSSMManagedInstanceCore managed policy, and VPC endpoints for ssm, ssmmessages, and ec2messages if your instances lack a NAT gateway. Once those prerequisites are met, you’ve eliminated the bastion host as a single point of compromise—without sacrificing operational convenience.
EC2 Instance Connect: SSH with Ephemeral Keys and IAM Integration
EC2 Instance Connect solves the two ugliest problems with traditional bastions: key sprawl and the dreaded “who still has access to production?” audit question. It does this by pushing a one-time public key to the target instance’s metadata service. That key remains valid for exactly 60 seconds—just long enough to authenticate your session—then vaporizes. No persistent .pem files sitting on developer laptops, no shared keys floating around Slack, and no manual cleanup when someone leaves the team.
The access control layer shifts entirely to IAM. You write a policy that says “Alice can push a key to instances tagged env=staging,” and that policy gates every connection attempt. Revoking access means updating one IAM policy, not rotating keys across a fleet. For teams accustomed to terminal workflows, the behavioral change is minimal—developers keep using the SSH client they already know, just with a different command prefix.
But here’s the trade-off: you still need an open SSH port. You can scope the inbound rule to the Instance Connect service IP range (published by AWS) rather than the entire internet, which shrinks the attack surface considerably, but the port is still listening. And unlike Session Manager, EC2 Instance Connect doesn’t natively log shell commands—you’ll need to layer on something like auditd or a separate logging agent if you need keystroke-level audit trails for compliance. For teams that want to modernize access without abandoning SSH muscle memory, this is the pragmatic middle ground—stronger identity controls than a traditional bastion, less architectural change than going fully agent-based.
How to Choose: Session Manager vs. EC2 Instance Connect vs. Traditional Bastion
Every security-conscious AWS architect eventually hits the same fork in the road: do you deploy a traditional bastion, wire up EC2 Instance Connect, or go agent-native with Session Manager? The right answer pivots less on technical capability than on what your auditor needs to see.
The Criteria That Matter
Your decision boils down to four variables: protocol fidelity, your existing identity stack, logging granularity, and whether your fleet can run an agent. If you need raw SSH or RDP with full protocol negotiation—perhaps for a legacy .NET app that demands Windows Remote Desktop—a traditional bastion or EC2 Instance Connect will preserve that fidelity. Session Manager, by contrast, proxies a browser-based shell or tunnels SSH, which covers 95% of operational use cases but won’t satisfy tools that expect native RDP session handshakes.
Your IdP investment tilts the scale heavily. EC2 Instance Connect and Session Manager both integrate with AWS IAM and, by extension, any SAML or OIDC provider federated into your account—enforcing MFA without a single SSH key to rotate. A traditional bastion forces you back into the business of key lifecycle management, which is precisely what long-lived credentials enable. According to Verizon’s Data Breach Investigations Report, stolen credentials remain a top breach vector year after year.
| Criterion | Traditional Bastion | EC2 Instance Connect | Session Manager |
|---|---|---|---|
| Inbound ports required | 22 / 3389 open | 22 open (briefly) | None (outbound HTTPS only) |
| Key management | Manual; full burden | Ephemeral keys pushed via metadata | IAM roles; no keys |
| Session logging | DIY (auditd, third-party) | CloudTrail for push events | Full to S3/CloudWatch Logs |
| MFA enforcement | Layered (PAM required) | Via IAM policy condition | Via IAM policy condition |
| Agent dependency | None | None | SSM Agent required |
| Cost beyond EC2 | Instance + data transfer | No added charge | No added charge (S3/Logs at standard rates) |
Where Traditional Bastions Still Earn Their Keep
Don’t dismiss the old model outright. A traditional bastion remains the correct call when you’re dealing with lift-and-shift workloads that predate cloud-native tooling—think third-party network appliances, vendor-supplied AMIs that lock you out of installing the SSM Agent, or compliance regimes demanding protocol-level inspection that a proxy can’t satisfy. If your security team requires a bump-in-the-wire IPS to parse SSH traffic in transit, only a traditional bastion gives you that intercept point.
The Compliance-Forward Recommendation
For greenfield AWS environments targeting SOC 2 or PCI DSS, start with Session Manager as your default. It closes the inbound port attack surface entirely, ties every session to an auditable IAM principal, and ships command logs straight into your evidence-collection pipeline. When your QSA asks to see access controls during an audit, handing them a CloudTrail trail with session-level granularity beats reconstructing bastion host logs from a fleet you may have already decommissioned.
Locking Down the Bastion When You Have No Other Choice
If your security team has already flagged your bastion host in a recent audit, you don’t need to tear it down tomorrow—but you do need to shrink its attack surface before the next review. The single highest-impact move you can make is replacing 0.0.0.0/0 in your security group with the CIDR block of your corporate VPN endpoint. According to the Center for Internet Security, restricting ingress to known IP ranges remains one of the top three critical controls for internet-facing systems, and it eliminates the drive-by SSH brute-force noise that clutters your logs within minutes.
Beyond network-level restrictions, the real hardening happens at the authentication layer. Switch to short-lived SSH certificates issued by a lightweight CA like HashiCorp Vault’s SSH secrets engine or ssh-keygen with a custom CA. Certificates can embed principals, validity windows as tight as five minutes, and mandatory auditing metadata. Pair this with just-in-time credential issuance so no standing access exists for an attacker to steal.
Logging is non-negotiable. Ship /var/log/auth.log to CloudWatch Logs and configure metric filters that fire an SNS alert on repeated authentication failures or logins from unexpected source IPs. If you’re running a Windows bastion, forward Event ID 4625 (failed logon) and 4624 (successful logon) events. The goal is real-time anomaly detection, not post-mortem forensics.
Finally, treat the bastion as disposable infrastructure. Bake an immutable AMI with no persistent state, deploy it via an Auto Scaling group of one, and let a cron job or AWS Health event replace the instance if configuration drift is detected. When the instance is cattle, not a pet, compromise becomes an inconvenience—not a crisis.
What a Full Session Manager Deployment Looks Like in Practice
If you only configure the IAM role and walk away, you’ve left a backdoor open. A full Session Manager deployment isn’t about enabling access—it’s about systematically disabling every other path in.
The IAM Foundation
Attach the AmazonSSMManagedInstanceCore policy to your EC2 instance role. This grants the three permissions the SSM Agent needs: ssm for session control, ssmmessages for data channel communication, and ec2messages for command delivery. Without all three, the agent silently fails.
Network Path Without Internet Gateways
If your instances sit in private subnets—and they should—create VPC endpoints for com.amazonaws.[region].ssm, ssmmessages, and ec2messages. These three endpoints let the SSM Agent reach the service backend without routing through a NAT gateway or exposing anything to the public internet. Attach a security group to each endpoint that permits inbound HTTPS (443) from your instance security groups only.
Verify the Agent
Amazon Linux 2 and 2023 AMIs ship with the SSM Agent pre-installed, but don’t assume it’s running. Check the Systems Manager console under Fleet Manager—if an instance shows “Not managed,” the agent is stopped, missing, or lacks permissions. A quick sudo systemctl status amazon-ssm-agent confirms it on the instance itself.
Locking the Front Door: SCPs That Enforce the Path
This is the step that transforms Session Manager from a convenience into a compliance control. In your production AWS Organization unit, attach a Service Control Policy that explicitly denies ec2:AuthorizeSecurityGroupIngress and ec2:CreateKeyPair. The result: no engineer—regardless of IAM permissions—can open an inbound SSH port or generate a stray key pair. Access becomes Session Manager or nothing.
Audit Trail That Survives an Audit
In the Session Manager preferences, route session logs to an S3 bucket with SSE-KMS encryption and versioning enabled. For real-time visibility, also stream to CloudWatch Logs. The resulting records are tamper-evident: every keystroke, command, and output gets captured with a timestamp and the federated user’s identity. When your SOC 2 assessor asks, “Who accessed this instance and what did they do?”, you hand them a structured log, not a shrug.
The User Experience Test
Open a terminal and run aws ssm start-session --target i-1234567890abcdef0. You’re now in a bash shell with no inbound security group rule, no bastion host, and no SSH key. Need to tunnel RDP or a database port? Append --document-name AWS-StartPortForwardingSession --parameters '{"portNumber":["3389"],"localPortNumber":["13389"]}'. You’ve forwarded a port through an encrypted TLS 1.2 tunnel without opening a single inbound rule—and every action is logged.


