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 two 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 public Network Address Translation (NAT) Gateway (this article) - A public Network Address Translation (NAT) Gateway allows EC2 instances that do NOT have a public IP address to access the internet. The public NAT Gateway will have an Elastic IP (a public IP address)

At a high level, it can look like this.

 


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.

 

The aws ec2 create-subnet command can be used to create a subnet. Let's create a second subnet in 172.0.0.0/24 CIDR.

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

 

The aws ec2 create-route-table command can be used to create another Route Table in your Virtual Private Cloud (VPC). Let's create a new Route Table.

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).

 

The aws ec2 associate-route-table command can be used to update the Route Table associated with a Subnet. Let's update the second Subnet to be associated to the Route Table that only has the target local route. Now 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).

 

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.

 

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 3b4859 in the box below so that we can be sure you are a human.