Securely Accessing EC2 Windows Instances via SSH Port Forwarding
Reaching an EC2 Windows instance in a private subnet through an SSH bastion host, keeping RDP off the public internet entirely.
Placing an EC2 Windows instance in a private subnet and accessing it through a bastion host in a public subnet is a common pattern for securing remote desktop access without exposing the instance directly to the internet.
This method secures only the connection between the client and the bastion host. No encryption is applied between the bastion host and the EC2 Windows instance.
The overall architecture is illustrated below.

Building Backend
VPC
Create a private subnet within the target VPC. This step can be skipped if a suitable subnet already exists.

Create a route table and associate it with the private subnet.

Ensure the internet gateway is removed from this route table; its presence would render the subnet effectively public.

If the EC2 Windows instance requires internet access, create a NAT gateway in a public subnet and attach it to the route table.
Running NAT gateway instances incurs additional costs.


SSH Bastion Host
- Launch an EC2 instance in the public subnet to serve as the bastion host.
- Configure the security group to allow inbound traffic on ports 22 (SSH) and 3389 (RDP).
Assigning an Elastic IP (EIP) makes access to the bastion host easy.
EC2 Windows Instance
- Launch an EC2 Windows instance in the private subnet.
- Retrieve the remote desktop credentials using the
Get Windows Passwordoption in the EC2 dashboard. - Restrict security group access to allow inbound traffic on ports 22 and 3389 only from the bastion host.

Testing the Connection
To establish a secure connection to the EC2 Windows instance, execute the following command from a local terminal:
ssh -i <YOUR_PRIVATE_KEY> -L 13389:<YOUR_EC2_WINDOWS_IP>:3389 ec2-user@<YOUR_SSH_BASTION_IP>This command forwards traffic from local port 13389 to the EC2 Windows instance in the private subnet via the bastion host. A remote desktop session can then be initiated by connecting to localhost:13389.


Conclusion
Putting the EC2 Windows instance in a private subnet with no internet gateway route, then reaching it through an SSH bastion in the public subnet, keeps RDP off the public internet entirely. The key piece of this setup is the security group on the private instance: restricting ports 22 and 3389 to traffic from the bastion host only, rather than to a broader CIDR range, is what actually enforces that. ssh -L 13389:<EC2_WINDOWS_IP>:3389 then does the rest, tunneling the RDP session through the bastion so localhost:13389 on the client machine reaches an instance that has no route in from outside the VPC. Since the tunnel only encrypts the client-to-bastion hop, the bastion host itself is the piece worth hardening most carefully — a tight security group and, ideally, an EIP reserved for known administrative access rather than an open range.
Related posts
Connecting to EC2 with Session Manager (No SSH Required)
Opening a shell on an EC2 instance through Systems Manager's Session Manager, with no SSH keys, bastion host, or open port 22 required.
Debugging PHP Remotely on AWS EC2 with PhpStorm and Xdebug
Remote debugging a PHP application on EC2 with PhpStorm and Xdebug, from the server-side ini settings to the IDE's path mapping.
Running Proxy.py as a Lightweight HTTP Proxy on EC2
Running Proxy.py on an EC2 instance and reaching it safely through an SSH tunnel, since the proxy has no authentication of its own.
Sign in with Slack Using Cognito User Pools and OIDC
Federating Cognito user pools with Slack over OIDC and wiring "Sign in with Slack" into a Next.js app with Amplify.
Deploying FastAPI on AWS Lambda with Lambda Web Adapter
This example guides you through the process of developing API backends with FastAPI using Lambda Web Adapter.
