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,
- The Route53 private zone will be configured with a specific VPC, so AWS will attach the internal DNS servers to that VPC.
- Create a
A
record on the hosted zone to bind a private IP with a hostname. - Through a VPN Server, we will implement a secure, encrypted tunnel between the local workstation and the VPC.
- While we configure the AWS internal DNS information on the local machine for the DNS resolution.
- User will establish the VPN connection and try to query the hostname of the AWS Private resources which is present in the configured VPC.
- 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

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.