Tutorial: Setting up a private subnet on AWS

For growing companies that offer services over the internet, there often comes the point where they need to distinguish between what services they offer to the public and what services to offer only internally.

Especially when you’re building a micro-service infrastructure, you want that most services can talk to each other, but not that the entire internet can talk to them. The most typical way of doing this, is to ask the user for some kind of authentication, e.g. a password. The first downsides that come with passwords is that you have to management (e.g. when employees are joining and leaving your company) and they don’t protect you from attacks like DDoS.

One solution that I would like to talk about here are private subnets:

You want some of your machines/services to be accessible from the public internet, e.g. your web servers, but want to shield other machines, e.g. databases form direct web access. Often it is helpful to allow services in the private subnet to “call-out”, but not to allow systems from the outside to “call in” (see the red vs green-dashed arrows in the depiction above).

Your account

Since this is a tutorial on setting up a private subnet on AWS, you should be logged into the AWS user interface. I will assume that you have broad access rights in your account (e.g. you’re an administrator) and will not go into detail which rights you will need.

If you are worried that you might break a running system that already is in your account you have 2 choices. Create a new account for this tutorial or pick a region where there are no production systems running (the latter is obviously much less work!)

Pick a region

In AWS there are many different regions and probably you have already decided for one. If not here are my 2 cents on this topic:

  1. You should pick a region that is close to your customers and therefore reduce latency
  2. Picking the “older” and more established regions often gives you early access to new product launches and in some cases less headaches (e.g. new S3 authentication mechanisms). Good candidates are: us-east-1 and eu-west-1

Creating new subnets

We will create 2 new subnets in this tutorial one public, one private. If you have a completely new AWS account, you could also just use the already existing subnet as the public subnet, but I will assume you create a new one.

In the AWS Web UI go to ServicesVPC (virtual private cloud)Subnets

  1. Click the blue Create Subnet button at the top of the page
  2. You can chose what ever name you like, I would suggest the following naming scheme: REGION-PURPOSE-ACCESS-AZ
    in my case this would be:
    region: us-east-1 
    purpose: general 
    access: public 
    availability zone: 1a 
    so I called it: us-east-1-general-public-1a
  3. Chose your VPC — typically you only have 1 at this stage
  4. Availability zone: I would suggest you pick a , but in most cases it doesn’t really matter — for better fault tolerance, you should probably create at least one more subnet that is connected to b after this tutorial
  5. CIDR: This is basically the IP address range for your subnet. As a very short tutorial: defines the range of IP address from to . The /16 at the end defines how many bits from the start of the IP address are fixed (the same for all addresses in the range). In this case 16 of 32 are fixed. Since IP addresses are written in blocks of 8 bits, this means that the first 2 blocks 172 and 0 must be present in all IP addresses in the range, but the remaining 2 blocks are allowed to change!

    Here some more examples: ranges from to ranges from to ranges from to ranges from to
    ranges from to 

    To find a suitable CIDR for your case, you have to pick a sub-range of your VPC’s CIDR (it’s show a few lines earlier in the UI) and ideally follows you existing subnets. Without looking at the other subnets in your account, an easy way to find a CIDR would be to take your VPC’s CIDR, e.g. and take 172.0.X.0/20 as your subnet CIDR. For X you can try through numbers 0, 16, 32, 64, … until the AWS UI stops complaining ;)
  6. Repeat all steps from 1–5 for the private subnet (replacing the public with private in the name)
  7. After having created both subnets, select the public subnet and above the list select ActionsModify auto-assign IP settings and set the checkbox: Auto-assign IPv4 to Enable auto-assign public IPv4 address

Done! You now have 2 fresh subnets.

Creating a NAT

In order to allow services from your private subnet to talk to the internet, we need a Network Address Translation device. You could use an EC2 instance for this, but let’s use a preconfigured one from AWS:

In the AWS Web UI go to ServicesVPCNAT Gateways

  1. Click Create NAT Gateway
  2. Select your public subnet that we just created — you can type the name of it or copy&paste the id. In my case I picked: us-east-1-general-public-1a
  3. Create a new EIP: an Elastic IP is basically just an IP address that stays the same — so that you can reboot the machine and IP stays the same. In our case we will just create a new one (you could also reuse an existing one).

    After creating the EIP, I would just that you copy and save the EIP’s identifier, in order to give it a name later, for keeping your account clean.
  4. Click Create NAT Gateway 

    Your NAT Gateway is created! The following step is just the before mentioned cleanup.
  5. Go to ServicesEC2Elastic IPs and filter for the EIP’s id you saved. Rename it for instance to NAT Gateway just to keep track what it’s being used for

Routing your traffic

Now in the last step of the setup process, we need to route where the traffic is going. For this we need to go to: ServicesVPCRoute Tables

  1. You can reuse the existing one, but in order to reduce the risk of breaking a running application, let’s create a new one. Click Create Route Table .
  2. Pick a name: In my case I just called it private-route-table
  3. Pick the same VPC where you created the subnets
  4. Click Create
  5. Now back in the Route Table overview, select the table you just created and in the information view at the bottom of the screen select RoutesEdit Routes
  6. Click Add Route
  7. Under Destination type: and under target select NAT Gateway and pick the one that you just created (typically you only have one!)

    The destination means that when trying to talk to any address on the internet (range to ) your machine should ask the NAT gateway to help!
  8. Click Save Routes
  9. Now back in the Route Table overview, still with your new route table selected, in the information view at the bottom of the screen select Subnet AssociationsEdit subnet associations
  10. Select your private subnet and click Save

Done! If you have a normal AWS account without making any changes in this section you’re done. To be save, let’s make sure that your public subnet also knows how to talk to the internet:

  1. In the overview screen on the Route Table page find the route table where the column Main is equal to Yes and select it.
  2. In the information view on the bottom select Routes and check there is a route with Destination and Target igw-XXXXXXX (where X can obviously mean any character)
  3. If this is the case, you are done here!
  4. If this is not the case, please follow through the steps of creating the private route table, except when choosing the Target, please select Internet Gateway instead of the NAT!


You are now finished setting everything up — but since there can always be a problem, let’s make sure it works by testing it!

Starting a server in our private subnet

Let’s start a new EC2 machine in our private subnet, by going to: ServicesEC2Instances

  1. Click Launch Instance
  2. You can launch any image, but I will assume you picked some kind of Ubuntu Server ... and pressed Select
  3. You can pick any machine size, but I suggest a t2-micro or t2-nano and click Next: Configure Instance Details
  4. The only option that we have to change here is to select our private subnet we created earlier. Now click: Next: Add Storage
  5. Nothing to change. Now click: Next: Add Tags
  6. I suggest you add a tag called Name with value my-private-instance this helps you finding this instance later! Now click: Next: Configure Security Group
  7. In the security group section, you have to make sure to set a security group that allows ssh access. If you don’t have any existing ones, you have to create a new one. AWS already selected everything for you, I would just suggest to change the name from launch-wizard-1 to ssh-access-only .
    Now click: Preview & Launch
  8. Now click: Launch
  9. AWS will now ask you for an ssh key-pair. Either pick an existing one or create a new one. If you create a new one, please follow these steps:

    a) Put in a name, e.g. your name :P
    b) Press Download 
    c) On a mac or unix machine you would now go to the terminal and run:
    mv ~/Downloads/FILENAME ~/.ssh/NAME_YOUR_KEY.pem 
    chmod 600 ~/.ssh/NAME_YOUR_KEY.pem 
    ssh-add ~/.ssh/NAME_YOUR_KEY.pem 
    These steps will put the key into the normal folder, reduce access rights so that only you can use it and tells the ssh agent (service running on your machine) that it exists
  10. After having dealt with the SSH Key we can finally Launch Instances to start the machines.

We have now started a server in the private subnet — but we should not be able to connect to it via SSH directly, since the private subnet is… private.

In order to connect to it, we must launch another instance in the public subnet. Go through the same steps as launching the private machine, except for selecting the public subnet in step (4)!

Now you should have 2 instances running — 1 private and 1 public. The public one should also have a public ip and dns. This public server is typically called bastion and I will refer to it as such in the next steps.

To connect to your private machine, we first have to ssh into the bastion:


After typing the above 2 commands into your terminal, you should now be logged in on your private server. To validate that also the outgoing internet connection is working, just type ping into the terminal and should see output like this:

64 bytes from icmp_seq=34 ttl=108 time=12.6 ms
64 bytes from icmp_seq=35 ttl=108 time=12.6 ms
64 bytes from icmp_seq=36 ttl=108 time=12.6 ms
64 bytes from icmp_seq=37 ttl=108 time=12.5 ms
64 bytes from icmp_seq=38 ttl=108 time=12.5 ms
64 bytes from icmp_seq=39 ttl=108 time=12.5 ms

I hope it does! → if yes, we are done! 🎉🍾

otherwise I’m very sorry and please go back though the steps above to check what may have gone wrong.


  1. We created 2 subnets — a public and a private one
  2. Created a NAT Gateway to allow internet access for the private network
  3. Routed the traffic in the networks to get internet access
  4. Validated everything is working

Next Steps

Obviously this is only the start of the journey, but where can you go from here?

  1. If you want to connect to these private subnets from your office wifi, please go through this explanation: AWS Docs
    I will probably also clarify this in another Post, but until then, you can use the link above.
  2. You can now launch your database or other services with a much better protection layer
  3. You can launch private serverless (AWS Lambda) functions with regional endpoints. I will soon write a Post on this! This makes building micro-services architectures much easier!