Create a VPC with Nat Gateway in AWS

A Virtual Private Cloud or VPC is one of the basic elements to start building your infrastructure in the Cloud. It provides isolation for our resources and security.

When we create an account in AWS, a default VPC is set up on our behalf. This makes very easy to start creating resources in it, but after playing around, you will soon realize that you need a more customized network.

In the following lines, I will guide you step by step in the creation of all the resources you will need to create your own VPC using Cloudformation.

To make it easier, I’ve created a Cloudformation template that you can use and follow all the process. You will find it my GitHub: https://github.com/godof-cloud/aws-vpc-natgateway

Before start, let me show you how our VPC will look like:

AWS VPC Diagram

Our VPC will consist of 4 subnets (two public and two private), the fact that a subnet will be configured public or private will depend on which route table is associated. Basically, all the subnets associated with the routing table where the internet gateway is configured would be public.

Optionally, we can decide to create a NatGateway, this resource would give internet connectivity to our private subnets.

Now that we know what we are going to build, let’s get our hands dirty …

Parameters

This will be the first part of our template, here we define custom values to configure the VPC.

prefix:
  Type: String
  Default: custom
sgCidr:
  Type: String
  Default: 0.0.0.0/0
vpcCidrBlock:
  Type: String
subnetCidrBlocks:
  Type: List<String>
vpcCidrRange:
  Type: String
  Default: 16
subnetCidrRange:
  Type: String
  Default: 24
createNatGateway:
  Type: String
  Default: false
  AllowedValues:
    - true
    - false
  • prefix: This value will be appended in the resource’s name that we are going to create to be sure that there is any conflict with other resources that you might have, by default is custom.
  • sgCidr: Custom IP to configure the Security Group to allow access only from the specified IP that you can define, by default is 0.0.0.0/0 which means open to the internet.
  • vpcCidrBlock: IP block for the VPC network , for example, 10.0.0.0
  • subnetCidrBlocks: IP blocks for the subnets separated by commas, for example, 10.0.1.0, 10.0.2.0, 10.0.3.0, 10.0.4.0
  • vpcCidrRange: IP range for the VPC, by default 16.
  • subnetCidrRange: IP range for the Subnets, by default 24.
  • createNatGateway: Flag to decide if the template would create a NatGateway.

Conditions

Conditions:
  EnabledNatGateway: !Equals [ !Ref createNatGateway, 'true' ]

Because the creation of the resources for the NatGateway depends on the value of createNatGateway parameter, in the Conditions section we use the Ref Intrinsic Function to get the value of the parameter and the Equals Intrinsic Function to check if the value equals true.

Doing that we initialize the variable EnabledNatGateway with the result and we will use this variable in some resources to specify that should be only created when the condition is satisfied.

If you want to know more about Intrinsic Functions I recommend you have a look at the Cloudformation documentation in this link: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

Resources

VPC

VPC:
  Type: AWS::EC2::VPC
  Properties:
    CidrBlock: !Join ["/", [!Ref vpcCidrBlock, !Ref vpcCidrRange]]
    EnableDnsHostnames: true
    EnableDnsSupport: true
    Tags:
      - Key: "Name"
        Value: !Join ["-", [!Ref prefix, "vpc"]]

This block creates the VPC, when the property EnableDnsHostnames is set to true the EC2 instances that are created in the VPC get DNS hostnames and the EnableDnsSupport indicates whether the DNS resolution is supported for the VPC.

If you kept the value of the parameter vpcCidrRange to 16, that means that our VPC will be able to host 65536 IPs approximately.

Security Group

DMZSecurityGroup:
  Type: AWS::EC2::SecurityGroup
  Properties:
    GroupName:  !Join ['-', [!Ref prefix, 'dmz-sg']]
    GroupDescription: DMZ Security Group to allow Access to SSH
    VpcId: !Ref VPC
    SecurityGroupIngress:
      - Description: Allow SSH
        IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: !Ref sgCidr
      - Description: Allow Http
        IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: !Ref sgCidr
      - Description: Allow Https
        IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        CidrIp: !Ref sgCidr

The creation of a Security Group is not really necessary, but I think is very useful at least to have a default one attached to the VPC. This security group allows SSH, HTTP and HTTPS access from the custom IP configure by the sgCidr parameter.

Subnets

Subnet1:
  Type: AWS::EC2::Subnet
  Properties:
    VpcId: !Ref VPC
    AvailabilityZone: !Select 
      - 0
      - !GetAZs 
        Ref: 'AWS::Region'
    CidrBlock: !Join ["/", [!Select [0, !Ref subnetCidrBlocks], !Ref subnetCidrRange]]
    Tags:
      - Key: "Name"
        Value: !Join ["-", [!Ref prefix, "subnet1"]]

In these blocks, we configure the subnets that will be created in our VPC, in this example we’ve set 4 subnets but we can add more depending on our own requirements.

Here, we use the Select Intrinsic Function to select in which Availability Zone of our Region we want to assign our subnet, in this example we will alternate our subnets between the first and the second Availability Zone of the Region, we manage that changing the index of the Select function.

Bear in mind that the Availability Zones in the Regions are not the same in all the AWS Accounts

For the subnets IP, we do something similar. We use the list provided by subnetCidrBlocks parameter to get the respective value.

Route Tables

PublicRT:
  Type: AWS::EC2::RouteTable
  Properties:
    VpcId: !Ref VPC
    Tags: 
      - Key: "Name"
        Value: !Join ["-", [!Ref prefix, "public", "table"]]
  
PrivateRT:
  Type: AWS::EC2::RouteTable
  Properties:
    VpcId: !Ref VPC
    Tags: 
      - Key: "Name"
        Value: !Join ["-", [!Ref prefix, "private", "table"]]

“A route table contains a set of rules, called routes, that are used to determine where network traffic from your subnet or gateway is directed”

AWS

For this example, we are going to create two route tables. In one of them, we will link later an Internet Gateway and the subnets that we want to expose to the internet and we keep the other for those subnets that we want to have in our private area of the VPC.

Subnet Association

RouteSubnet1:
  Type: AWS::EC2::SubnetRouteTableAssociation
  Properties:
    RouteTableId: !Ref PublicRT
    SubnetId: !Ref Subnet1

RouteSubnet3:
  Type: AWS::EC2::SubnetRouteTableAssociation
  Properties:
    RouteTableId: !Ref PrivateRT
    SubnetId: !Ref Subnet3

This is an example of subnet association, the first one links the subnet number one with the public Route Table and the second association sets the subnet number three with the private Route Table. We do the same for subnets numbers two and four.

Internet Gateway

InternetGateway:
  Type: AWS::EC2::InternetGateway
  Properties:
    Tags: 
      - Key: "Name"
        Value: !Join ["-", [!Ref prefix, "ig"]]
  
VpcIgAttachment:
  Type: AWS::EC2::VPCGatewayAttachment
  Properties:
    InternetGatewayId: !Ref InternetGateway
    VpcId: !Ref VPC

RouteIG:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

As I explained before, in order to provide Internet connectivity to our subnets, we need to use an Internet Gateway. To achieve that first, we create an Internet Gateway resource, then we attach it to our VPC and finally, we configure the association between our IG and the destination cidr block 0.0.0.0/0 in the public Route Table.

Nat Gateway

ElasticIP:
  Type: AWS::EC2::EIP
  Condition: EnabledNatGateway
  Properties:
    Domain: vpc

NatGateway:
  Type: AWS::EC2::NatGateway
  Condition: EnabledNatGateway
  Properties:
    AllocationId: !GetAtt ElasticIP.AllocationId
    SubnetId: !Ref Subnet1

RouteNatGateway:
  Type: AWS::EC2::Route
  DependsOn: [ NatGateway ]
  Condition: EnabledNatGateway
  Properties:
    DestinationCidrBlock: '0.0.0.0/0'
    NatGatewayId: !Ref NatGateway
    RouteTableId: !Ref PrivateRT

This section is optional and only will be created if it’s set the value true in the createNatGateway parameter. You can see that these resources have the Condition field in its definition assigned with the variable EnabledNatGateway that we defined in the Conditions section of our template.

Maybe you would be asking yourself, why would I need to create a NatGateway? Well, probably you won’t need to create one unless you need to internet connectivity in your private subnets.

But NatGateways are not cheap and keep it 24/7 can increase your bill substantially, so that’s why I decided to give you the option the create it only when you need it and then delete it easily just changing the parameter to false.

There are cheaper options like Nat Instances, but then you have to take care of the instance patching and autoscaling.

To create a NatGateway we need to attach to it an Elastic IP and place it one of our public subnets, in this example Subnet1. Also, we have to add a new route in our Private Route Table that associates our NatGateway with the destination 0.0.0.0/0

Outputs

VPCId:
  Value: !Ref VPC
  Export:
    Name: !Sub "${AWS::StackName}-vpc-id"

Subnet1Id:
  Value: !Ref Subnet1
  Export:
    Name: !Sub "${AWS::StackName}-subnet1-id"

Subnet2Id:
  Value: !Ref Subnet2
  Export:
    Name: !Sub "${AWS::StackName}-subnet2-id"
  
Subnet3Id:
  Value: !Ref Subnet3
  Export:
    Name: !Sub "${AWS::StackName}-subnet3-id"
  
Subnet4Id:
  Value: !Ref Subnet4
  Export:
    Name: !Sub "${AWS::StackName}-subnet4-id"

PublicRTId:
  Value: !Ref PublicRT
  Export:
    Name: !Sub "${AWS::StackName}-public-rt-id"
  
PrivateRTId:
  Value: !Ref PrivateRT
  Export:
    Name: !Sub "${AWS::StackName}-private-rt-id"

DMZSecurityGroup:
  Value: !Ref DMZSecurityGroup
  Export:
    Name: !Sub "${AWS::StackName}-dmz-sg"

DefaultSecurityGroup:
  Value: !GetAtt VPC.DefaultSecurityGroup
  Export:
    Name: !Sub "${AWS::StackName}-default-sg"

Last but not least, we can define some outputs that our template will expose. These outputs can be used by other templates to make references to the resources that we have created.

We need to define a name for each output and for convention we use the stack name of the template as a prefix to avoid name conflicts. To get the stack name we use the Sub Intrinsic Function that substitutes the reference AWS::StackName automatically.

In this example, we’ve configured outputs to expose our Vpc Id, the IDs for our 4 subnets and the Security Group Identifier.

Conclusion

Having a custom VPC in our cloud environment is one of the basic pillars for our AWS infrastructure, in this example, we’ve set up a basic configuration, but you can modify this template to adapt it to your own requirements.

Adding more subnets, setting other IPs and different IP ranges, perhaps having more granular Route Tables or even assigning some ACLs (Access Control List) to our subnets to have an extra layer of security.

4

25 Replies to “Create a VPC with Nat Gateway in AWS”

  1. Thank you for another informative website. Where else could
    I get that type of info written in such a perfect method?
    I’ve a project that I am simply now operating on, and I’ve been on the glance out for such information.

    1. Many thanks for your comment and welcome to this exciting world of cloud builders.
      My main references basically are the AWS documentation and some A Cloud Guru courses that I bought in Udemy, especially the one to get the Solutions Architect Associate certification.

      But stay tuned because soon I will publish a new post to give an example of how to update automatically your ECS containers when you upload a new image to ECR 😉

  2. Long time reader, first time commenter — so, thought I’d drop a comment..
    — and at the same time ask for a favor.

    Your wordpress site is very simplistic – hope you don’t
    mind me asking what theme you’re using? (and don’t mind if I steal it?
    :P)

    I just launched my small businesses site –also built in wordpress like
    yours– but the theme slows (!) the site down quite a
    bit.

    In case you have a minute, you can find it by searching for “royal cbd” on Google (would appreciate any feedback)

    Keep up the good work– and take care of yourself during the coronavirus scare!

    ~Justin

    1. Hi Justin,
      Many thanks for your support, as you can see I’ve also started very recently in the WordPress world so I know how painful can be.
      My theme is not a secret, I’m using the twentyseventeen, one of the preinstalled WordPress themes.

      I really like be minimalist and I found it very convenient, but I guess any of the pre-installed themes are quite good.

      I’ve checked your site and it’s very nice, I like the combination of gold and black. Obviously, your site is more oriented to an online shop so the amount of information and images that you have to show can slow the loading of the site.

      If the products and information that you show don’t change very often, I would advise you to use some kind of cache or content delivery network to help with the loading of the static information like images (usually those are the resources that takes more time to load), I know there are some plugins for WordPress that does the trick.
      If you are interested in this page explains which options do you have: https://wordpress.org/support/article/optimization-caching/

      I wish the best with your business, especially during this time when everything seems to be in standby because of the COVID-19.

      Best regards and stay safe,
      Javier

  3. Its such as you learn my mind! You appear to know a lot about this, such as
    you wrote the guide in it or something. I believe that you just could do with a few p.c.
    to power the message home a bit, however instead
    of that, that is wonderful blog. An excellent read. I will
    definitely be back.

  4. I am not sure where you’re getting your info, but good topic.

    I needs to spend some time learning more or understanding more.
    Thanks for wonderful info I was looking for this information for my
    mission.

    1. Many thanks for the support, it’s all based on my experience with AWS. I know how difficult beginnings are with a new technology, so I hope with these posts I can make it more accessible 🙂

  5. Hey! Do you use Twitter? I’d like to follow you if that would
    be ok. I’m absolutely enjoying your blog and look forward
    to new posts.

    1. Sure! My twitter is @godof_cloud, you can find the link as well at the bottom of any page (probably I should put it in a more visible place ^^) I hope in the next days publish a new entry, so stay tuned 😉

    1. Many thanks for your support! I’m glad to hear that you are enjoying the blog, there are still very few posts but I try to add new material every month ^^

  6. What’s Taking place i’m new to this, I stumbled upon this I’ve discovered
    It absolutely helpful and it has helped me out loads.
    I am hoping to give a contribution & help other customers like its helped me.
    Good job.

    1. Many thanks for your kind words, I started this blog to help others in its journey to the Cloud, so I’m really happy to hear that is helping you 🙂

  7. Thank you for the good writeup. It in fact was a amusement account it.
    Look advanced to more added agreeable from you! By the way, how could
    we communicate?

  8. Do you mind if I quote a few of your articles as long as I provide credit and sources back to your blog?
    My blog is in the exact same niche as yours and
    my users would definitely benefit from some of the information you present here.
    Please let me know if this alright with you. Cheers!

    1. Hi, sure you can quote me in your articles, also if you give me your blog I will have a look at your content, I’m sure I can learn a lot from you as well 😉

  9. I am sure this paragraph has touched all the internet
    users, its really really pleasant paragraph on building up new
    weblog.

  10. Hello to every one, the contents present at this web site
    are really awesome for people experience, well, keep up the good work fellows.

  11. Simply want to say your article is as surprising. The clearness for your publish is simply excellent and that
    i can suppose you’re a professional in this subject.
    Fine along with your permission allow me to
    snatch your feed to stay updated with forthcoming post.
    Thank you 1,000,000 and please keep up the rewarding work.

  12. Attractive section of content. I just stumbled upon your website and in accession capital to
    assert that I get in fact enjoyed account your blog posts.
    Anyway I will be subscribing to your augment and even I achievement you
    access consistently fast.

  13. I was excited to discover this site. I want to to thank
    you for ones time just for this wonderful read!!
    I definitely really liked every part of it and i also have you
    saved to fav to see new things on your web site.

Comments are closed.