Bootstrap FreeKB - Amazon Web Services (AWS) - Getting Started with Network Address Translation (NAT) Gateway
Amazon Web Services (AWS) - Getting Started with Network Address Translation (NAT) Gateway


At a high level, there are two types of subnets, public and private.

  • A public subnet has access to the internet
  • A private subnet does NOT have access to the internet

At a high level, there are a few ways to access the internet.

  • Using an Internet Gateway - An Internet Gateway allows EC2 instances that have a public IP address to access the internet.
  • Using a NAT Gateway - A Network Address Translation (NAT) Gateway translates a private IP address to a public IP address which allows EC2 instances that do not have a public IP address to access the internet by translating the EC2 instances private IP address to a public IP address
    • NAT Gateway (this article) - AWS NAT Gateway Service
    • NAT Instance - a dedicated EC2 Instance serves as the NAT Gateway

At a high level, it can look like this.

 

It is also noteworthy that Virtual Private Cloud (VPC) Endpoints can be used for communication between different AWS Services in one of your Virtual Private Clouds (VPC). The communication only occurs within Amazon, within your VPC - it never goes outside the Amazon network, never goes outside of your VPC, never gets onto the Internet. In this way, this is good from a privacy and security perspective. For example, a Virtual Private Cloud (VPC) Endpoint can be created for services such as

  • API Gateway Execute API - com.amazonaws.us-east-1.execute-api
  • S3 Bucket - com.amazonaws.us-east-1.s3
  • Simple Notification Service (SNS) - com.amazonaws.us-east-1.sns
  • Simple Queue Service (SQS) - com.amazonaws.us-east-1.sqs
  • et cetera

 


Virtual Private Cloud (VPC) CIDRs

In this walkthrough, we are going to have two subnets in the same Virtual Private Cloud (VPC), one with an Internet Gateway, the other with a public NAT Gateway, each in their own CIDRs.

  • the 172.31.0.0/16 subnet will have an EC2 instance with a public IP address and the EC2 instance will be able to access the Internet via an Internet Gateway
  • the 172.0.0.0/24 subnet will have an EC2 instance that does not have a public IP address and the EC2 instance will be able to access the Internet via a public NAT Gateway

The Virtual Private Cloud is going to need to be configured with both the 172.31.0.0/16 and 172.0.0.0/24 subnet. I go with this approach for clear separation between the Internet Gateway subnet vs. the public NAT Gateway subnet

 


INTERNET GATEWAY SUBNET

Let's start with an EC2 instance that has a public IP address and is using a Subnet with a Route Table that has an Internet Gateway Route, just for proof of concept to ensure the EC2 instance is able to connect to the Internet.

 

The aws ec2 create-subnet command can be used to create a subnet. Let's create a subnet in 172.31.0.0/16 CIDR.

aws ec2 create-subnet --vpc-id vpc-0a9d4cb29e2748444 --cidr-block 172.31.0.0/16 --availability-zone us-east-1a

 

By default, the subnet will have target local which allows communication between systems in the Virtual Private Cloud (VPC) and will NOT have a route to one of your Internet Gateways or Network Address Translation (NAT) Gateways. At this point, this is a private subnet that will not allow connections to the internet. Let's update the Route Table in the Subnet to have destination 0.0.0.0/0 and target one of your Internet Gateways in your Virtual Private Cloud (VPC). Now the Subnet is a public subnet.

 

Let's create an EC2 instance using the aws ec2 run-instances command and attach the EC2 instance to the subnet.

aws ec2 run-instances \
--image-id ami-0cf10cdf9fcd62d37 \
--count 1 \
--key-name default \
--instance-type t2.micro \
--subnet-id <subnet ID public subnet> \
--associate-public-ip-address \
--security-group-ids sg-11122233344455566677 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=public-instance}]'

 

SSH onto the EC2 instance and the ip address command should show that the instance has an IP address in the subnet (in this example, the EC2 instance has IP address 172.31.45.86 which is in the 172.31.0.0/16 subnet).

~]$ ip address
2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 0e:d1:fa:42:5f:37 brd ff:ff:ff:ff:ff:ff
    altname enp0s5
    altname eni-0d39a7ba2fef60763
    altname device-number-0
    inet 172.31.45.86/20 metric 512 brd 172.31.47.255 scope global dynamic ens5
       valid_lft 2645sec preferred_lft 2645sec
    inet6 fe80::cd1:faff:fe42:5f37/64 scope link
       valid_lft forever preferred_lft forever

 

The route --numeric command should contain a route that has destination 0.0.0.0. This is the Internet Gateway route.

~]$ route --numeric
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.31.32.1     0.0.0.0         UG    512    0        0 ens5 <- internet gateway (flag UG stands for Up Gateway, the Gateway Route is Up)
172.31.0.2      172.31.32.1     255.255.255.255 UGH   512    0        0 ens5 <- local (flag UGH stands for Up Gateway Host, the Gateway Host Route is Up)
172.31.32.0     0.0.0.0         255.255.240.0   U     512    0        0 ens5 <- ? (flag U stands for Up, the route is Up)

 

You should be able to ping remote URLs, such as www.example.com. Hooray - so far, so good!

[ec2-user@ip-172-31-45-86 ~]$ ping -c4 www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=3 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=4 ttl=54 time=23.4 ms

--- www.example.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 23.444/23.453/23.472/0.187 ms

 


PRIVATE SUBNET - (no internet access)

Now let's create a second EC2 instance that, by default, will be in a Subnet with a Route Table that does not have an Internet Gateway or a Network Address Translation (NAT) Gateway. We are doing this for proof of concept, to prove that the EC2 instance in the private subnet has no Internet access. Then, we'll update this subnet with a public NAT Gateway.

 

Let's use the the aws ec2 create-subnet command to create a subnet. The default VPC that get's created when you create your AWS account has CIDR 172.31.0.0/16 so we need to create a subnet with a different CIDR. In this example, I'm going with CIDR 172.0.0.0/24.

aws ec2 create-subnet --vpc-id vpc-0a9d4cb29e2748444 --cidr-block 172.0.0.0/24 --availability-zone us-east-1a

 

Let's use the aws ec2 create-route-table command to create a Route Table in your Virtual Private Cloud (VPC).

aws ec2 create-route-table --vpc-id vpc-0a9d4cb29e2748444

 

By default, the new Route Table should have target local which allows communication between systems in the Virtual Private Cloud (VPC) and does NOT have a route to one of your Internet Gateways or Network Address Translation (NAT) Gateways. This Route Table is private (no internet access).

 

Let's use the aws ec2 associate-route-table command to update the Route Table to be associated with Subnet you just created. The subnet is private with no internet access.

aws ec2 associate-route-table --route-table-id rtb-0e96e9343c4086863 --subnet-id subnet-0f015da3a1e164304

 

The aws ec2 describe-security-group-rules command can be used to list the Security Groups Rules with a particular Security Group. The Security Group that will be associated with the EC2 instance in the private subnet will need to allow incoming SSH connections from the EC2 instances in the public subnet.

[ec2-user@ip-172-31-45-86 ~]$ aws ec2 describe-security-group-rules --filter Name="group-id",Values="sg-0808005cec92e15d2"
{
    "SecurityGroupRules": [
        {
            "SecurityGroupRuleId": "sgr-09d0b3825b80b67b7",
            "GroupId": "sg-0808005cec92e15d2",
            "GroupOwnerId": "123456789012",
            "IsEgress": false,
            "IpProtocol": "tcp",
            "FromPort": 22,
            "ToPort": 22,
            "ReferencedGroupInfo": {
                "GroupId": "sg-0778124087b3d14d4",
                "UserId": "123456789012"
            },
            "Description": "ssh",
            "Tags": []
        }
    ]
}

 

Let's create an EC2 instance using the aws ec2 run-instances command ensuring the EC2 instance is associated with the private subnet and the Security Group that will allow us to SSH onto the EC2 instance in the private subnet from the EC2 instance in the public subnet. 

Both EC2 instance will need to use subnets that are in the same availability zone in the same Virtual Private Cloud (VPC). For example, both would need to be in Availability Zone us-east-1. It's perfectly OK for the EC2 instance to be in different sub-availability zones, such as us-east-1a and us-east-1b.

aws ec2 run-instances \
--image-id ami-0cf10cdf9fcd62d37 \
--count 1 \
--key-name default \
--instance-type t2.micro \
--subnet-id <subnet ID private subnet> \
--security-group-ids sg-11122233344455566677 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=private-instance}]'

 

SSH onto the EC2 instance in the public subnet and you should still be able to ping remote URLs, such as www.example.com. Hooray - still all good!

[ec2-user@ip-172-31-45-86 ~] ping -c4 www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=3 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=4 ttl=54 time=23.4 ms

--- www.example.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 23.444/23.453/23.472/0.187 ms

 

From the EC2 instance in the public subnet SSH onto the EC2 instance in the private subnet. 

Let's say you have a file named aws.ppk that is being used to connect to your EC2 instance in your public subnet using PuTTY. Assuming both EC2 instances are Linux system, you will need to convert the aws.ppk file to an OpenSSH private key file such as aws.key. This can be done using PuTTYgen. Check out my article PuTTYgen - Extract public certificate and private key from PPK file.

[ec2-user@ip-172-31-45-86 ~] ssh -i /home/ec2-user/.ssh/aws.key ec2-user@10.0.0.8
   ,     #_
   ~\_  ####_        Amazon Linux 2
  ~~  \_#####\
  ~~     \###|       AL2 End of Life is 2025-06-30.
  ~~       \#/ ___
   ~~       V~' '->
    ~~~         /    A newer version of Amazon Linux is available!
      ~~._.   _/
         _/ _/       Amazon Linux 2023, GA and supported until 2028-03-15.
       _/m/'           https://aws.amazon.com/linux/amazon-linux-2023/

[ec2-user@ip-172-0-0-5 ~]$

 

On the EC2 instance in the private subnet, you should NOT be able to ping www.example.com since the EC2 instance in the private subnet does not have a route that allows connections out of the private subnet, and www.example.com is outside of the private subnet.

[ec2-user@ip-172-0-0-5 ~]$ ping -c4 www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.

--- www.example.com ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3072ms

 


PUBLIC NETWORK ADDRESS TRANSLATION (NAT) GATEWAY

Now let's update the private subnet to have a public NAT Gateway so that the EC2 instance can access the Internet. A public NAT Gateway will be associated with an Elastic IP (a public IP address).

 

Be aware that NAT Gateway does incure a modest charge. I had a single NAT Gateway that ended up costing me a little over $30.00 per month.

 

If you are concerned about cost, you probably wouldn't want to create VPC Endpoints as an alternative option. I created a few VPC Endpoints because I needed to call a few different AWS Services such as S3 (for S3 Buckets) and Secrets Manager (to get the value is some secrets) and STS (to assume role) and so on, and you have to create a separate VPC Endpoints for each service, and after only 5 days the cost for the VPC Endpoints was the same as what it cost me for the NAT Gateway for the entire month! ugh. So I quickly got to work on looking at a cheaper option, and that's when I realized I create create a EC2 NAT Instance which would only cost me like $4 per month (or less than $2 per month as a Reserved Instance) and this is exactly what I ended up doing here.

 

The Network Address Translation (NAT) Gateway will need to be associated with an Elastic IP (a public IP address) for it to be a public NAT gateway. The aws ec2 allocate-address command can be used to allocate an Elastic IP address.

aws ec2 allocate-address

 

The aws ec2 create-nat-gateway can be used to create a Network Address Translation (NAT) Gateway and associated the NAT Gateway with the Elastic IP address. Also, be certain to create the NAT Gateway in your public subnet (172.31.0.0/16 in this example), not in your private subnet.

aws ec2 create-nat-gateway --subnet-id subnet-11122233344455566 --allocation-id eipalloc-11122233344455566

 

Now let's update the Route Table to have the public NAT Gateway. Check out my article Amazon Web Services (AWS) - EC2-Other Cost Breakdown for more details on Cost Explorer.

 

The aws ec2 describe-security-group-rules command can be used to list the Security Groups Rules with a particular Security Group. The Security Group that will be associated with the EC2 instance in the private subnet with a Network Address Translation (NAT) Gateway route will need to allow incoming SSH connections from the EC2 instances in the public subnet and allow outbound to 0.0.0.0/0.

[ec2-user@ip-172-31-45-86 ~]$ aws ec2 describe-security-group-rules --filter Name="group-id",Values="sg-0808005cec92e15d2"
{
    "SecurityGroupRules": [
        {
            "SecurityGroupRuleId": "sgr-09d0b3825b80b67b7",
            "GroupId": "sg-0808005cec92e15d2",
            "GroupOwnerId": "123456789012",
            "IsEgress": false,
            "IpProtocol": "tcp",
            "FromPort": 22,
            "ToPort": 22,
            "ReferencedGroupInfo": {
                "GroupId": "sg-0778124087b3d14d4",
                "UserId": "123456789012"
            },
            "Description": "ssh",
            "Tags": []
        },
        {
            "SecurityGroupRuleId": "sgr-0f360cfdddfad5c64",
            "GroupId": "sg-0808005cec92e15d2",
            "GroupOwnerId": "123456789012",
            "IsEgress": true,
            "IpProtocol": "-1",
            "FromPort": -1,
            "ToPort": -1,
            "CidrIpv4": "0.0.0.0/0",
            "Tags": []
        }
    ]
}

 

For proof of concept, let's create an EC2 instance using the aws ec2 run-instances command, and attach the EC2 instance to the public subnet.

Both EC2 instance will need to use subnets that are in the same availability zone in the same Virtual Private Cloud (VPC). For example, both would need to be in Availability Zone us-east-1. It's perfectly OK for the EC2 instance to be in different sub-availability zones, such as us-east-1a and us-east-1b as long as the subnets have the same CIDR, such as 10.0.0.0/24.

aws ec2 run-instances \
--image-id ami-0cf10cdf9fcd62d37 \
--count 1 \
--key-name default \
--instance-type t2.micro \
--subnet-id <subnet ID private subnet> \
--security-group-ids sg-11122233344455566677 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=private-instance}]'

 

SSH onto the EC2 instance in the public subnet and you should still be able to ping remote URLs, such as www.example.com. Hooray - still all good!

[ec2-user@ip-10-0-0-22 ~]$ ping -c4 www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=3 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=4 ttl=54 time=23.4 ms

--- www.example.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 23.444/23.453/23.472/0.187 ms

 

From the EC2 instance in the public subnet SSH onto the EC2 instance the subnet that has the Network Address Translation (NAT) Gateway.

The Security Group attached to the EC2 instance in the private subnet will need to allow SSH connections from the EC2 instance in the public subnet. This can either be done by allowing connections from the IP address of the EC2 instance in the public subnet or allowing connections from the Security Group associated with the EC2 instance in the public subnet.

Let's say you have a file named aws.ppk that is being used to connect to your EC2 instance in your public subnet using PuTTY. Assuming both EC2 instances are Linux system, you will need to convert the aws.ppk file to an OpenSSH private key file such as aws.key. This can be done using PuTTYgen. Check out my article PuTTYgen - Extract public certificate and private key from PPK file.

[ec2-user@ip-10-0-0-22 ~]$ ssh -i /home/ec2-user/.ssh/aws.key ec2-user@10.0.0.8
   ,     #_
   ~\_  ####_        Amazon Linux 2
  ~~  \_#####\
  ~~     \###|       AL2 End of Life is 2025-06-30.
  ~~       \#/ ___
   ~~       V~' '->
    ~~~         /    A newer version of Amazon Linux is available!
      ~~._.   _/
         _/ _/       Amazon Linux 2023, GA and supported until 2028-03-15.
       _/m/'           https://aws.amazon.com/linux/amazon-linux-2023/

[ec2-user@ip-10-0-0-8 ~]$

 

On the EC2 instance in the subnet that has the Network Address Translation (NAT) Gateway, you should be able to ping www.example.com, which shows the EC2 instance Internet access via the NAT Gateway. Awesome!

[ec2-user@ip-10-0-0-8 ~]$ ping -c4 www.example.com
PING www.example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=3 ttl=54 time=23.4 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=4 ttl=54 time=23.4 ms

--- www.example.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 23.444/23.453/23.472/0.187 ms

 




Did you find this article helpful?

If so, consider buying me a coffee over at Buy Me A Coffee



Comments


Add a Comment


Please enter 33908c in the box below so that we can be sure you are a human.