This is an old but gold feature of SSM, but i’m continually surprised about how little good documentation there is only regarding it’s use cases. I hope this will help someone!
If you’re still running public bastion hosts or temporarily opening security groups to reach private resources, it’s time to stop. AWS Systems Manager (SSM) Session Manager supports audited, ephemeral port tunneling that works over the instance’s outbound connection to SSM.
Below are practical, production‑ready use cases with exact commands and minimal setup.
SSM establishes an outbound, TLS‑encrypted session from the managed instance to the SSM service. Because this is established outbound from the instance, that means no inbound Security Groups are required! That’s right, zero inbound rules necessary!
Port forwarding then binds a local port on your workstation and transports traffic over that session either to:
No inbound SG rules or public IPs are required. All access is IAM‑authorized and fully auditable.
ec2-user
, ubuntu
) and the instance’s SSH server is enabled (reachable locally from the instance itself).Minimal IAM for users (attach via group or role):
{
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": [
"ssm:StartSession", "ssm:TerminateSession", "ssm:DescribeSessions", "ssm:GetConnectionStatus"
], "Resource": "*"},
{"Effect": "Allow", "Action": [
"ssmmessages:CreateControlChannel", "ssmmessages:CreateDataChannel", "ssmmessages:OpenControlChannel", "ssmmessages:OpenDataChannel",
"ec2messages:AcknowledgeMessage", "ec2messages:DeleteMessage", "ec2messages:FailMessage", "ec2messages:GetEndpoint", "ec2messages:GetMessages", "ec2messages:SendReply"
], "Resource": "*"}
]
}
Best practice: use SSM as a ProxyCommand so SSH rides the SSM session.
Notes:
You can write the config to your .ssh/config
file for simple repeated access!
Add this to ~/.ssh/config
:
Host i-*
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
IdentityFile ~/.ssh/ec2_private_key.pem
User ec2-user
Then:
ssh i-04025b02105b29a2a
In one terminal window, start the session:
aws ssm start-session --target <INSERT INSTANCE ID HERE> \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["22"],"localPortNumber":["9999"]}'
You can then SSH into the instance:
ssh -i ~/.ssh/ec2_private_key.pem -p 9999 ec2-user@localhost
Forward localhost:5432 to a private Aurora/RDS database endpoint.
aws ssm start-session \
--target <INSERT INSTANCE ID HERE> \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["mydb.cluster-abcdefghijkl.us-east-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["5432"]}'
Connect with your local client:
psql "host=127.0.0.1 port=5432 dbname=postgres user=commure sslmode=require"
Rather than using psql, you can also connect via a DB Client:
Tips:
Setup Port forwarding session on EC2 Instance, Then a DB Client SSH Proxy setting to connect through to the database.
In one terminal window, start the session:
aws ssm start-session --target <INSERT INSTANCE ID HERE> \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["22"],"localPortNumber":["9999"]}'
You can then create your DB connection config in your DB Client of choice like in the image above.
Then configure the SSH Tunnel settings like so:
Tunnel RDP and connect your standard client.
No public RDP, no bastion.
aws ssm start-session \
--target <INSERT INSTANCE ID HERE> \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["3389"],"localPortNumber":["13389"]}'
Open Microsoft Remote Desktop to 127.0.0.1:13389
via RDP client:
Forward HTTPS to a VPC‑only service (managed or self‑hosted). Example: Amazon OpenSearch Service domain restricted to the VPC.
aws ssm start-session \
--target <INSERT INSTANCE ID HERE> \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["vpc-logs-abc123.us-east-1.es.amazonaws.com"],"portNumber":["443"],"localPortNumber":["8443"]}'
Then browse: https://localhost:8443
.
Swap the host/port for Grafana (3000), Kibana (5601), internal admin UIs, etc.
Use VS Code’s Remote - SSH extension with SSM as the transport to get a full remote workspace—file browsing, editing, terminals, and port forwarding—without opening inbound ports.
Install the VS Code extension: “Remote - SSH” (ms-vscode-remote.remote-ssh).
Ensure your SSH config uses SSM as a ProxyCommand (see Use case 1). Add a friendly alias for VS Code:
Host vscode-ec2
HostName <INSERT INSTANCE ID HERE>
User ec2-user
IdentityFile ~/.ssh/ec2_private_key.pem
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
vscode-ec2
→ choose the remote OS. VS Code will install the server on the instance over the SSM-backed SSH session.Command Palette - Connect to Host
OR
You can locate your hosts on the left panel like so:
Command Palette - Connect to Host
localhost:<port>
).Notes:
A dynamic SOCKS proxy lets your apps connect to any host:port on demand through a single local proxy. Instead of forwarding one fixed destination (-L
), SOCKS (-D
) can reach many internal services without creating new tunnels each time.
-L
?~/.ssh/config
:
Host ssm-proxy
HostName <INSERT INSTANCE ID HERE>
User ec2-user
IdentityFile ~/.ssh/ec2_private_key.pem
ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
Start the SOCKS proxy:
ssh -N -D 127.0.0.1:1080 ssm-proxy
# optional: background, keepalive, fail fast on bind
# ssh -f -N -D 127.0.0.1:1080 -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 ssm-proxy
Point your tools at the proxy:
127.0.0.1:1080
(enable “proxy DNS” / “SOCKS remote DNS”).curl --socks5-hostname 127.0.0.1:1080 https://internal.service.example
export ALL_PROXY=socks5h://127.0.0.1:1080
export NO_PROXY=localhost,127.0.0.1
Notes:
socks5h
/ --socks5-hostname
resolve DNS via the proxy (prevents local DNS leaks).127.0.0.1
) so it isn’t exposed to your LAN.Benefits:
-L
forwards for web UIs that fetch assets from multiple hostsBuilding on the SOCKS proxy from use case 6, let’s connect to a private EKS cluster using it!
Run kubectl
from your laptop to a private‑only EKS API by sending traffic through the SOCKS proxy from Use case 6. No public endpoint, no bastion.
Prereqs:
aws eks get-token
).aws eks update-kubeconfig --name my-cluster --region us-east-1 --kubeconfig ~/.kube/eks-private
export KUBECONFIG=~/.kube/eks-private
ssh -N -D 127.0.0.1:1080 ssm-proxy
# optional: background, keepalive
# ssh -f -N -D 127.0.0.1:1080 -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 ssm-proxy
export ALL_PROXY=socks5h://127.0.0.1:1080
export NO_PROXY=localhost,127.0.0.1
kubectl get ns
kubectl get nodes -o wide
You can also use local tools like Lens to view and make changes to your cluster!
Lens connected via SOCKS Proxy
Notes:
socks5h
ensures DNS for the EKS endpoint is resolved via the proxy (no local DNS leaks).127.0.0.1
).aws eks get-token
) runs locally and does not require the proxy; only the Kubernetes API traffic is proxied.Turn on logs now
Set tight session limits
Least privilege IAM
Environment=dev
) and allowed documents (AWS-StartPortForwardingSession*
, AWS-StartSSHSession
).Kill inbound admin ports
Pick the right jump host
Avoid DNS leaks
socks5h
/ --socks5-hostname
; enable “Proxy DNS” in browsers.Keep sessions healthy
-o ServerAliveInterval=60 -o ExitOnForwardFailure=yes
.aws ssm get-connection-status --target <INSERT INSTANCE ID HERE>
Name and tag
Clean up when done
aws ssm describe-sessions --state Active --query 'Sessions[].{Id:SessionId,Target:Target}'
aws ssm terminate-session --session-id <ID>
SSM port tunneling replaces bastions and ad‑hoc firewall changes with auditable, IAM‑controlled, temporary access. The patterns above cover day‑to‑day needs—from SSH and databases to UI access, SOCKS proxies, and remote debugging—without ever opening inbound ports.
Cut the surface area, keep the logs, and move faster.