AWS Route53 Private Hosted Zone - Beginners Guide

Route53 Private Hosted Zone

In this blog, we will learn about the AWS Route53 Private hosted zone and its use cases and hands-on.

In general, the AWS Route53 service is used for DNS resolution.

For example, when you try to open a website e.g., dev.techiescamp.com, the behind IP will be resolved over the DNS servers on the internet.

But what if you are using a private network and only need to resolve the DNS query within the network.

Here, the AWS Route53 Private hosted zone comes into the picture, where the DNS resolution will happen in the AWS internal DNS server and only within the VPC.

AWS Route53 Hosted Zone Workflow

The workflow diagram explains the AWS Route53 private hosted zone configurations as well as the integration of a VPN Server.

The above workflow diagram explains,

  1. The Route53 private zone will be configured with a specific VPC, so AWS will attach the internal DNS servers to that VPC.
  2. Create a A record on the hosted zone to bind a private IP with a hostname.
  3. Through a VPN Server, we will implement a secure, encrypted tunnel between the local workstation and the VPC.
  4. While we configure the AWS internal DNS information on the local machine for the DNS resolution.
  5. User will establish the VPN connection and try to query the hostname of the AWS Private resources which is present in the configured VPC.
  6. The DNS resolution will happen in the Route53 private hosted zone and securely reach the destination.

Before we setup the private hosted zone on the Route53, we need a VPC.

Set up a VPC for the Route53 Private Hosted Zone

The below steps will give configurations to create a simple VPC setup, but you can use the exisiting VPC if you already have.

For the demonstration purposes, I am creating a new one using a simple AWS Cloudformation template.

The following template will create a VPC with one public and private subnet. The public subnet will have the Internet Gateway for inbound and outbound internet access.

The server in the public subnet will act as a jump server for the private server.

The private subnet will only have the NAT gateway for the external internet access.

The private subnet we use for testing the private server.

Create a Cloudformation template file vpc-template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: Template to create one VPC with a public and private subnet

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: Main-VPC

  # Public Subnet
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: Public-Subnet

  # Private Subnet
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: Private-Subnet

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: VPC-IGW

  # Attach Internet Gateway to VPC
  GatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # Elastic IP for NAT Gateway
  NatEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  # NAT Gateway in Public Subnet
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatEIP.AllocationId
      SubnetId: !Ref PublicSubnet
      Tags:
        - Key: Name
          Value: NAT-Gateway

  # Public Route Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Public-Route-Table

  # Associate Public Route Table with Public Subnet
  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

  # Route to Internet via IGW for Public Subnet
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Private Route Table
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Private-Route-Table

  # Associate Private Route Table with Private Subnet
  PrivateSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet
      RouteTableId: !Ref PrivateRouteTable

  # Route to NAT Gateway for Private Subnet
  PrivateRouteToNat:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

Outputs:
  VPCId:
    Description: ID of the VPC
    Value: !Ref VPC
  PublicSubnetId:
    Description: ID of the Public Subnet
    Value: !Ref PublicSubnet
  PrivateSubnetId:
    Description: ID of the Private Subnet
    Value: !Ref PrivateSubnet
  NatGatewayId:
    Description: ID of the NAT Gateway
    Value: !Ref NatGateway

To deploy this, use the following command.

aws cloudformation create-stack --stack-name VPCStack --template-body file://vpc-template.yaml --region us-east-1

The creation will take a few minutes to complete.

In the meantime, you can see the creation status over the dashboard of the Cloudformation.

Once the creation is completed, we can check it over the VPC dashboard.

Here, we can see the created VPC Main-VPC and its subnets.

The private subnet is attached to the NAT Gateway.

The public subnet is attached to the Internet Gateway.

How to Set up an AWS Route53 Private Hosted Zone

Follow the below steps to setup a private hosted zone in AWS Route53.

Step 1: Create a Private Hosted Zone

Open the AWS Route53 service and select the Hosted zones tab from the left side panel.

On the following window, click the Create hosted zone button to create a new hosted zone.

Provide a domain name that you want techiescamp.com, you can provide any name.

Select the types as Private hosted zone and on the VPC association section, select the region of the VPC and its ID.

Once provide all the required information, click the Create hosted zone button to create the private hosted zone.

After the successful creation of the private hosted zone, two records are available by default.

What are they?

The first one is the NS which represents the Name Server Record, these records define the internal name servers that are responsible for the DNS resolution only within the VPC.

The second one is the SOA which represents the Start of Authority Records, this will provide the metadata about the hosted zone, such as admin contact, serial number, and timing values(refresh, retry, and expire).

AWS Route53 Private Hosted Zone Testing

The private hosted zone setup is completed, but how can we test it, well we can create a simpe Nginx server on the private subnet and map the IP of that server to the Route53 private hosted zone.

Then try to access it from the public server using the hostname.

For that, we need to create an EC2 instance on both public and private subnets.

Step 1: Create Test Servers on Both Subnets

You can manually open the EC2 console and create instances on both subnets, but for now, I am using a Cloudformation template for that.

Create a Cloudformation template file ec2-instance-template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: Launch Ubuntu EC2 instances in public and private subnets

Parameters:
  VPCId:
    Type: String
    Description: ID of the existing VPC
  PublicSubnetId:
    Type: String
    Description: ID of the existing Public Subnet
  PrivateSubnetId:
    Type: String
    Description: ID of the existing Private Subnet
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Name of an existing EC2 KeyPair to enable SSH access

Resources:
  # Security Group for Ubuntu Public EC2
  UbuntuPublicSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Ubuntu Public EC2
      VpcId: !Ref VPCId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: Ubuntu-Public-SG

  # Security Group for Ubuntu Private EC2
  UbuntuPrivateSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Ubuntu Private EC2
      VpcId: !Ref VPCId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref UbuntuPublicSG
      Tags:
        - Key: Name
          Value: Ubuntu-Private-SG

  # Ubuntu EC2 Instance in Public Subnet (with Public IP)
  UbuntuPublicInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      KeyName: !Ref KeyName
      ImageId: ami-084568db4383264d4  
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref PublicSubnetId
          GroupSet:
            - !Ref UbuntuPublicSG
      Tags:
        - Key: Name
          Value: Ubuntu-Public-Server

  # Ubuntu EC2 Instance in Private Subnet (no public IP)
  UbuntuPrivateInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      KeyName: !Ref KeyName
      ImageId: ami-084568db4383264d4  
      SubnetId: !Ref PrivateSubnetId
      SecurityGroupIds:
        - !Ref UbuntuPrivateSG
      Tags:
        - Key: Name
          Value: Ubuntu-Private-Server

Outputs:
  UbuntuPublicInstanceId:
    Description: ID of the Ubuntu EC2 in public subnet
    Value: !Ref UbuntuPublicInstance

  UbuntuPrivateInstanceId:
    Description: ID of the Ubuntu EC2 in private subnet
    Value: !Ref UbuntuPrivateInstance

To deploy this, use the following command.

 aws cloudformation create-stack \   
  --stack-name EC2InstanceStack \
  --template-body file://ec2-instance-template.yaml \
  --parameters \
    ParameterKey=VPCId,ParameterValue=<VPC_ID> \
    ParameterKey=PublicSubnetId,ParameterValue=<PUBLIC_SUBNET_ID> \
    ParameterKey=PrivateSubnetId,ParameterValue=<PRIVATE_SUBNET_ID> \
    ParameterKey=KeyName,ParameterValue=<KEY_PAIRS> \
  --capabilities CAPABILITY_NAMED_IAM \
  --region <REGION>

Change the VPC ID, Subnets, and Key name with your values.

Step 2: Install an Nginx Web Server on the Private Server

Since the private server doesn't have public access, we can only access it from the public server.

So open the public server and SSH, to the private server, but before that note down the private server's IP address.

Now open the public server.

Select the EC2 Instance Connect option to connect the public server through the web terminal.

Create a .pem file in the public server and store the public key contents.

Using the following command, we can SSH to the private server to install the Nginx server.

ssh -i key.pem ubuntu@<PRIVATE_SERVER_IP>

To install Nginx on the Ubuntu server, use the following command.

sudo apt update
sudo apt install nginx -y

To check the service after the installation,

systemctl status nginx.service

This ensures that the service is active and running, so now we need to check the localhost access.

curl localhost
s

You can see the welcome page, which means that the web server page is actively loading on the private server itself.

Now, we need to check it directly from the public server.

So, you need to exit from the private server using the following command.

exit

Before directly accessing the Nginx server from the public server, ensure the private server's security group allows port 80 to the public server.

Once the security group rules are correctly set, we can access the private Nginx server from the public server using its private IP.

curl <PRIVATE_SERVER_IP>

But this is not we want, we need to access this private Nginx server via a domain name.

Step 3: Map Private IP to the Hostname on Route53

We can map the same private IP to the Route53 private hosted zone, so that we can access this Nginx server using the hostname.

When we try to access, the DNS resolution will happen by the internal DNS servers, which AWS manages.

To map the private IP, navigate to the Route53 console private hosted zone and create a new DNS record.

Create a prefix for the record name e.g., nginx and select the record type as A which will map the IP with hostname.

In the Value section, we need to add the IP of the private Nginx server, then click to Create records to create the record.

It will take a few minutes to complete and then you can see the record on the dashboard.

Now, we can access the Nginx server from the public server using this hostname instead of the IP address.

curl <HOST_NAME>

It is working, but what if I want to access it from my local machine? First, you need to establish a VPN connection between your local machine and the AWS VPC.

How to Leverage the Route53 Private Hosted Zone capabilities from the local Machine

We need a VPN connection between our local machine and the specific VPC Main-VPC. For that, I have used Pritunl Self-hosted VPN server, but you can use any VPN setup like OpenVPN, AWS Client VPN Endpoint, etc.

To know about the setup of the OpenVPN in an EC2 instance you can refer to this blog.

Once the VPN connection is established, we can access the private Nginx server from our local machine by using its private IP as like we did in the public cluster.

Here, you can see that the DNS resolution is not happening. This is because the local system is not part of the AWS VPC and doesn't have DNS server details.

So, we need to add the DNS server details.

In the Pritunl configuration, we can provide the DNS server details, but how can we identify the DNS server information, such as its IP?

Well, for the custom DNS servers in the VPC, the IP of the DNS server is the second IP from the range. For example, if the VPC CIDR is 10.0.0.0/16, then the DNS server IP is 10.0.0.2.

To learn more about this, please refer to the official documentation.

We need to configure this for the DNS resolution on the VPN server configuration.

After changing the DNS server on the VPN configuration, when you establish the VPN connection from your local machine, your default DNS server configuration on the local machine is automatically changed, and you can check from the /etc/resolve.conf

This ensures that the resolution will happen only through this DNS server.

Now, we can check access from the local machine using the hostname.

Not only check from the terminal, but you can also directly check from the web browser.

Bonus Configuration

Instead of configuring the DNS server information directly to the VPN server, we can only configure it in our local machine to harden the security.

First, we need to keep the DNS details on the VPN server dashboard empty.

Now, configure the DNS server information on the local machine.

I am setting up the MAC, but it would be similar to every operating system.

On the DNS configuration section, add the DNS server IP

After adding the DNS server details on the local machine, we can use the hostname to access the private server resources in a secure manner.

After the hands-on, you can use the following commands to destroy the setup.

aws cloudformation delete-stack --stack-name EC2InstanceStack --region us-east-1
aws cloudformation delete-stack --stack-name VPCStack --region us-east-1

Conclusion

This is the effective method of using the Route53 Private hosted zone to access the private resources from the local machine.

You can use any VPN server to communicate between the local machine and the AWS VPC.

A single hosted zone creation will cost $0.50 per month. This will be the same up to 25 hosted zones, and if it goes beyond that, each new one will cost an additional $0.10 per month.

About the author
Arun Lal

Arun Lal

Arun Lal is a DevOps Engineer & AWS Community Builder, also an Expert in AWS infrastructure, Terraform automation, and GitLab CI/CD pipelines.

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to DevOpsCube – Easy DevOps, SRE Guides & Reviews.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.