How to connect to EC2 instance in private subnet

While deploying our applications on an EC2 instance, we often use the approach where we deploy the frontend of our application in the public subnet and the backend of our application in the private subnet. The perk of this approach is that we make our application more secure. The backend logic, which communicates between the frontend and the database, is not accessible to anyone in this way. However, since EC2 instances in a private subnet don’t have a public IP address, we can’t connect to them using SSH. So, to resolve this issue, we use the VPC endpoint.

The basic infrastructure

The basic infrastructure for our application consists of a VPC with a public and a private subnet. We will then create the internet gateway and a NAT gateway in our VPC, and after that, we will configure the routing tables. The internet gateway allows the instances and the resources to communicate over the internet, and the NAT gateway acts as a channel between the resources in the private subnet to communicate over the internet. Then, we will deploy an instance in the private subnet. The instance deployed in the private subnet has no public IP address, so we can’t connect to it using conventional methods. To establish a connection with the private subnet, we need to create a VPC endpoint. We will create an “Instance Connect Endpoint” in the VPC to establish a connection with the EC2 instance. The infrastructure is similar to the illustration below:

Architecture diagram
Architecture diagram

CloudFormation template

Here, we will create the infrastructure using AWS CloudFormation. CloudFormation allows us to deploy infrastructure as code. Let’s construct a CloudFormation template with the required resources.

Parameters

Parameters are a way to pass dynamic values into the template at runtime. They allow you to customize and modify the behavior of the resources defined in your CloudFormation template without changing it. We’ll use the parameters to pass values where they are required so that they are easy to manage and update.

Parameters:
VpcCidrBlock:
Type: String
Default: "10.1.0.0/16"
PublicSubnetCidrBlock:
Type: String
Default: "10.1.1.0/24"
PrivateSubnetCidrBlock:
Type: String
Default: "10.1.2.0/24"
InstanceAMI:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64'
AllowedValues:
- '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64' # Replace with your actual AMI ID
InstanceType:
Type: String
Default: 't2.micro'

Here, we define the VPC CIDR block, public and private subnet CIDR blocks, the instance’s AMI, and the instance type.

VPC

We’ll create our own isolated virtual network, which will be associated with the resources.

cfnVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidrBlock
EnableDnsHostnames: true
EnableDnsSupport: true

Here, we create the VPC with the name cfnVPC which uses the CIDR block VpcCidrBlock, defined in the parameters. We enabled the DNS support and hostnames for any public instances that might reside in this network.

Internet gateway and NAT gateway

We’ll create an internet gateway so that our resources are accessible to the internet traffic. Creating an internet gateway allows the internet traffic to and from the VPC. We’ll also create a NAT gateway that will allow the private instance to communicate over the internet.

InternetGateway:
Type: AWS::EC2::InternetGateway
NATGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATGatewayEIP.AllocationId
SubnetId: !Ref PublicSubnet
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: cfnVPC
InternetGatewayId:
Ref: InternetGateway
NATGatewayEIP:
Type: AWS::EC2::EIP

Here, we create an internet gateway and NAT gateway. The internet gateway is defined on lines 1–2, and the NAT gateway is defined on lines 4–8. We associate the NAT gateway with a public subnet and attach an elastic IP address to the NAT gateway, which is created on lines 18–19. On lines 10–16, we attach the internet gateway to the VPC.

Subnets

Subnets are essentially smaller segments of the available address space within your VPC, providing an additional layer of control and security for resources in our environment. Here, we’ll create a private and a public subnet.

PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref cfnVPC
CidrBlock: !Ref PublicSubnetCidrBlock
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref cfnVPC
CidrBlock: !Ref PrivateSubnetCidrBlock

Here, we create public and private subnets in the cfnVPC with the CIDR blocks defined in the parameters section.

Route table

A route table defines which external IP addresses can be reached from a subnet or internet gateway.

PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref cfnVPC
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NATGateway

Here, we create route tables for public and private subnets. The public route is associated with the internet gateway, directing traffic to 0.0.0.0/0 via the Internet Gateway. The private route is associated with the NAT gateway, directing traffic to 0.0.0.0/0 via the NAT Gateway.

Subnet Association

Subnet association refers to the process of linking a subnet to a specific route table in a Virtual Private Cloud (VPC) within Amazon Web Services (AWS). This association determines the routing rules that apply to the traffic originating from the subnet.

PublicSubnetAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
PrivateSubnetAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable

Here, we are associating the route tables with the subnets. The PublicSubnetAssociation resource associates the public subnet with the public route table. The PrivateSubnetAssociation resource associates the private subnet with the private route table.

Security group

A security group functions as a virtual firewall for our instance, managing inbound and outbound traffic. The security group outlined below permits all traffic over port 22 (SSH) and port 80 (HTTP). This security group is necessary for instances in both the private and public subnets.

MySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Default security group for cfnVPC"
VpcId: !Ref cfnVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0

Here, we define the security group with the identifier MySecurityGroup and on lines 6–10, we are allowing inbound traffic on port 22, which is designated for SSH connections.

EC2 Instance Connect Endpoint

An Instance Connect Endpoint is an AWS feature that allows secure and easy access to your Amazon EC2 instances without the need to manage SSH keys or modify security group rules.

EC2Endpoint:
Type: AWS::EC2::InstanceConnectEndpoint
DependsOn: PrivateInstance
Properties:
SecurityGroupIds:
- !Ref MySecurityGroup
SubnetId: !Ref PrivateSubnet

Here, we are creating the EC2 endpoint. On lines 5–6, we associate it with our security group. On line 7, we associate it with the private subnet.

Private instance

After all the necessary infrastructure has been defined, we can set up our EC2 instance. The EC2 instance configuration is provided below:

PrivateInstance:
Type: AWS::EC2::Instance
DependsOn: NATGateway
Properties:
ImageId: !Ref InstanceAMI
InstanceType: t2.micro
SecurityGroupIds:
- !GetAtt MySecurityGroup.GroupId
BlockDeviceMappings:
- DeviceName: "/dev/xvda"
Ebs:
VolumeType: "gp2"
DeleteOnTermination: "false"
VolumeSize: "8"
SubnetId: !Ref PrivateSubnet

Here, we are setting the instance type to t2.micro and the AMI we are using is amazon linux 2023 which we defined in the parameter section. On lines 7–8, we attach the security group to the instance. On lines 9–14, we define the block storage associated with the instance. On line 15, we associate the instance with the private subnet.

Putting it all together

The CloudFormation template that we are going to deploy is available in the widget below:

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
VpcCidrBlock:
Type: String
Default: "10.1.0.0/16"
PublicSubnetCidrBlock:
Type: String
Default: "10.1.1.0/24"
PrivateSubnetCidrBlock:
Type: String
Default: "10.1.2.0/24"
InstanceAMI:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64'
AllowedValues:
- '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64' # Replace with your actual AMI ID
InstanceType:
Type: String
Default: 't2.micro'
Resources:
cfnVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidrBlock
EnableDnsHostnames: true
EnableDnsSupport: true
InternetGateway:
Type: AWS::EC2::InternetGateway
NATGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATGatewayEIP.AllocationId
SubnetId: !Ref PublicSubnet
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: cfnVPC
InternetGatewayId:
Ref: InternetGateway
NATGatewayEIP:
Type: AWS::EC2::EIP
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref cfnVPC
CidrBlock: !Ref PublicSubnetCidrBlock
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref cfnVPC
CidrBlock: !Ref PrivateSubnetCidrBlock
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref cfnVPC
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref cfnVPC
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NATGateway
PublicSubnetAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
PrivateSubnetAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
MySecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Default security group for cfnVPC"
VpcId: !Ref cfnVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
EC2Endpoint:
Type: AWS::EC2::InstanceConnectEndpoint
DependsOn: PrivateInstance
Properties:
SecurityGroupIds:
- !Ref MySecurityGroup
SubnetId: !Ref PrivateSubnet
PrivateInstance:
Type: AWS::EC2::Instance
DependsOn: NATGateway
Properties:
ImageId: !Ref InstanceAMI
InstanceType: t2.micro
SecurityGroupIds:
- !GetAtt MySecurityGroup.GroupId
BlockDeviceMappings:
- DeviceName: "/dev/xvda"
Ebs:
VolumeType: "gp2"
DeleteOnTermination: "false"
VolumeSize: "8"
SubnetId: !Ref PrivateSubnet

Architecture deployment using AWS CLI

You can deploy the template by clicking the “Run” button in the “Deploy stack” playground below and pasting the following command in the terminal:

aws cloudformation create-stack --stack-name cfn-template-deploy --template-body file://Cloudformation_Template.yaml --region us-east-1
Create stack

In this command, cfn-template-deploy is the name of the template we are deploying and file://CloudformationTemplate.yaml is the path to the YAML file, which contains the blueprint of the infrastructure. This command will provide you with the stack ID.

To monitor the template, paste the following command in the terminal:

./status.sh
Shell script to monitor stack creation

This command executes the shell script, which logs the stack’s status after 30 seconds interval.

Enter your AWS Access_Key_ID and Secret_Access_Key in the widget below before running any commands. If you don’t have these keys, follow this Answer to generate them "How to generate AWS access keys."

STACK_NAME="cfn-template-deploy"
REGION="us-east-1"

while true; do
    STATUS=$(aws cloudformation describe-stacks --stack-name $STACK_NAME --region $REGION --query "Stacks[0].StackStatus" --output text)
    
    if [ "$STATUS" == "CREATE_COMPLETE" ]; then
        echo "Stack creation complete."
        break
    elif [ "$STATUS" == "ROLLBACK_COMPLETE" ]; then
        echo "Stack creation failed. Check the CloudFormation console for details."
        break
    else
        echo "Current status: $STATUS"
        sleep 30  # Wait for 30 seconds before checking again
    fi
done
Deploy stack

Create the endpoint using the console

If you already have the infrastructure deployed and you want to create the endpoint using the console, follow the steps listed below:

  • Search for “VPC” on the AWS Management Console, then click the “VPC” service from the search results.

  • Click “Endpoints” from the left menu bar and click the “Create endpoint” button.

  • Set the name for the endpoint in the “Name tag” section, and select “EC2 Instance Connect Endpoint” in the “Service category” section.

  • Choose the VPC where you intend to deploy the endpoint in the “VPC” section.

  • In the “Security groups” section, select the endpoint’s security group, and in the “Subnet’’ section, select the private subnet.

The slides below show how we can configure the EC2 Instance Connect Endpoint using the AWS console:

VPC dashboard
VPC dashboard
1 of 4

Connection to the EC2 using endpoint

As the instance resides in the private subnet, it is not reachable via the internet or shell. Therefore, we must establish a connection using the console. Follow the steps below to connect to the EC2 instance in the private subnet:

  • Search for “EC2” on the AWS Management Console, then click the “EC2” service from the search results.

  • Click “Instances” from the left menu bar and select the instance created in the private subnet.

  • Click the “Connect” button, and in the “Connection type” section, select the “Connect using EC2 Instance Connect Endpoint” option.

  • In the “EC2 Instance Connect Endpoint” section, click “Select an endpoint” and select the available endpoint from the list.

  • Scroll to the bottom and click the “Connect” button.

The slides below show how we can connect to the EC2 instance using the Instance Connect Endpoint:

Instances
Instances
1 of 3

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved