Migrate DNS records in bulk to an Amazon Route 53 private hosted zone - AWS Prescriptive Guidance
Services or capabilities described in AWS documentation might vary by Region. To see the differences applicable to the AWS European Sovereign Cloud Region, see the AWS European Sovereign Cloud User Guide.

Migrate DNS records in bulk to an Amazon Route 53 private hosted zone

Ram Kandaswamy, Amazon Web Services

Summary

This pattern automates the bulk migration of DNS records into an Amazon Route 53 hosted zone. It uses a Python script with the AWS SDK for Python (Boto3) to read record data from a JSON file stored in Amazon Simple Storage Service (Amazon S3) and create the records programmatically by using the Route 53 API.

If your source system exports standard BIND zone files, the Route 53 native import feature or cli53 (a community-maintained, open-source CLI tool not affiliated with AWS) can handle the migration without custom scripting. However, in many enterprise environments, DNS records live in Excel worksheets rather than zone files. Excel exports often contain data quality issues such as non-breaking spaces and Unicode characters, that standard import tools do not handle. This pattern addresses that gap: it reads from Excel, cleans the data, and uses only the AWS SDK for Python (Boto3) to create records end-to-end.

Use this pattern when you need to migrate a large number of DNS records from an existing system into a Route 53 hosted zone without creating each record manually through the console.

Prerequisites and limitations

Prerequisites 

  • An active AWS account

  • An Excel worksheet that contains hosted zone records with columns for fully qualified domain name (FQDN), record type, Time to Live (TTL), and value

  • Python 3.12 or later (see Download Python)

  • The pandas Python library installed (pip3 install pandas)

  • Familiarity with DNS record types such as A, NAPTR, and SRV records (see Supported DNS record types)

  • Familiarity with the Python language and its libraries

Limitations

  • This pattern does not use all available properties of the change_resource_record_sets API call. Extend the code as needed for your use case.

  • In the Excel worksheet, the value in each row is assumed to be unique. Multiple values for each FQDN are expected to appear in the same row. If that is not the case, modify the code to concatenate multiple values for the same FQDN.

  • This pattern calls the Route 53 API directly by using Boto3. You can enhance the code to use an AWS CloudFormation wrapper for the create_stack and update_stack commands and use the JSON values to populate template resources.

  • If your source system can export records in BIND zone file format consider using the Route 53 import feature instead.

  • Some AWS services aren't available in all AWS Regions. For Region availability, see AWS services by Region. For specific endpoints, see the Service endpoints and quotas page, and choose the link for the service.

Architecture

  1. The user uploads an Excel worksheet with DNS record data to an Amazon S3 bucket.

  2. A Python script running locally (or in an IDE such as Kiro) reads the JSON file from the Amazon S3 bucket by using Boto3.

  3. The script processes the records and calls the Route 53 change_resource_record_sets API operation to create records in the hosted zone.

Tools

AWS services

  • Route 53 –  Provides the hosted zone where DNS records are created. This pattern uses the create_hosted_zone and change_resource_record_sets API operations.

  • Amazon S3 - Stores the JSON file that contains the DNS record data to migrate.

Other tools

  • Kiro — An IDE with agentic AI capabilities, used to develop and run the Python migration script. For more information, see Kiro.

  • pandas — A Python data analysis library, used to convert the Excel worksheet to JSON format.

Best practices

  • Test with a small subset of 5–10 records before you run the script against all records to verify data formatting and correct record creation.

  • Use the UPSERT action instead of CREATE. The UPSERT action creates a record if it doesn't exist or updates it if it does. This makes the script idempotent and safe to rerun.

  • Batch your changes to stay within the ChangeResourceRecordSets request limits. When you use the UPSERT action, each ResourceRecord element counts twice toward the 1,000-element maximum.

  • Validate data before insertion. Verify that FQDNs end with a period (.), that TTL values are positive integers, and that record types match the supported DNS record types.

  • Back up existing records before you make bulk changes. Export the current hosted zone records by using list_resource_record_sets to provide a rollback point.

  • Implement retry logic with exponential backoff to handle Route 53 API rate limits. For more information, see Error retries and exponential backoff in AWS.

  • Follow the principle of least privilege and grant the minimum permissions required to perform a task. For more information, see Grant least privilege and Security best practices in the AWS Identity and Access Management (IAM) documentation.

Epics

TaskDescriptionSkills required

Create an Excel file for your records.

  1. Export records from your current DNS system.

  2. Create an Excel worksheet with these columns: FqdnName, RecordType, Value, TTL.

  3. For NAPTR and SRV records, use Excel's CONCAT function to combine multiple property values into the Value column.

  4. Verify that each FQDN ends with a period (.) and that TTL values are positive integers.

Example row:

FqdnName

RecordType

Value

TTL

something.example.org

A

192.0.2.1

900

Data engineer

Convert the Excel worksheet data to JSON.

  1. Verify that Python 3.9 or later is installed by running python3 --version in the terminal.

  2. Install the pandas library by running pip3 install pandas --user in the terminal.

  3. Create a Python file with the following code to convert the Excel worksheet to JSON:

import pandas as pd data=pd.read_excel('./Book1.xls') data.to_json(path_or_buf='my.json',orient='records')
  1. Run the script. Verify that the output file my.json contains the expected records.

Data engineer, Python developer

Upload the JSON file to an Amazon S3 bucket.

  1. Create an Amazon S3 bucket if one does not already exist. For more information, see Creating a bucket in the Amazon S3 documentation.

  2. Upload the my.json file to the bucket.

App developer
TaskDescriptionSkills required

Create a hosted zone.

  1. Create a Python file with the following code to create a hosted zone. Replace <hosted-zone-name>, <vpc-region>, and <vpc-id> with your values.

import boto3 import random hostedZoneName ="xxx" vpcRegion = "us-east-1" vpcId="vpc-xxxx" route53_client = boto3.client('route53') response = route53_client.create_hosted_zone( Name= hostedZoneName, VPC={ 'VPCRegion': vpcRegion, 'VPCId': vpcId }, CallerReference=str(random.random()*100000), HostedZoneConfig={ 'Comment': "private hosted zone created by automation", 'PrivateZone': True } ) print(response)
  1. Run the script and note the hosted zone ID from the response.

You can also use an infrastructure as code (IaC) tool such as AWS CloudFormation to replace these steps with a template that creates a stack with the appropriate resources and properties. For more information, see AWS Route 53::HostedZone in the CloudFormation documentation.

Cloud architect, Network administrator, Python skills

Read the JSON data from Amazon S3.

  1. Use this code to read from the Amazon S3 bucket and load it as a Python dictionary. 

import json import boto3 s3_client = boto3.client('s3') fileobj = s3_client.get_object( Bucket='<your-bucket-name>', Key='my.json' ) filedata = fileobj['Body'].read() contents = filedata.decode('utf-8') json_content = json.loads(contents)
  1. Verify that json_content contains the expected records by printing the first few entries.

App developer, Python developer

Clean data values and insert records.

  1. Use this code to clean data values and insert records into the hosted zone. Replace <hosted-zone-id> with the ID from the previously configured hosted zone.

import unicodedata import time for item in json_content: fqn_name = unicodedata.normalize( "NFKD", item["FqdnName"].replace("u'", "'").replace('\xa0', '').strip() ) rec_type = item["RecordType"].replace('\xa0', '').strip() res_rec = {'Value': item["Value"].replace('\xa0', '').strip()} change_response = route53_client.change_resource_record_sets( HostedZoneId='<hosted-zone-id>', ChangeBatch={ 'Comment': 'Created by automation', 'Changes': [ { 'Action': 'UPSERT', 'ResourceRecordSet': { 'Name': fqn_name, 'Type': rec_type, 'TTL': item["TTL"], 'ResourceRecords': [res_rec] } } ] } ) time.sleep(1) # Delay to avoid PriorRequestNotComplete errors
  1. Monitor the output for errors. If throttling occurs, increase the delay or implement exponential backoff.

App developer, Python skills

Troubleshooting

IssueSolution

PriorRequestNotComplete error

Add a time.sleep() delay of 1–2 seconds between change_resource_record_sets calls. Route 53 processes changes asynchronously, and a previous change may still be in progress.

InvalidChangeBatch error

This error occurs when you try to create a record that already exists by using the CREATE action. Use UPSERT instead to make the script idempotent.

Throttling error (rate limit exceeded)

You have exceeded five API requests per second. Implement exponential backoff with jitter. For more information, see Error retries and exponential backoff in AWS.

Records are not resolving

Verify that the VPC is associated with the hosted zone and that DNS resolution is enabled on the VPC. Both enableDnsSupport and enableDnsHostnames must be set to true.

InvalidInput error on record values

The data contains non-breaking spaces (\xa0) or leading/trailing whitespace. Apply the data cleaning code from the "Clean data values and insert records" task before insertion.

Related resources

References

Tutorials and videos 

DNS design using Amazon Route 53 (video)

Additional information

For NAPTR and SRV records, the Value field in the Excel worksheet must contain all required components concatenated into a single string. Use Excel's CONCAT function to combine the individual properties (order, preference, flags, service, regexp, replacement for NAPTR; priority, weight, port, target for SRV) before exporting to JSON.

If your migration involves more than 1,000 records, split the JSON file into batches and process each batch separately to stay within Route 53 API limits. When you use the UPSERT action, each ResourceRecord element counts twice toward the 1,000-element maximum per ChangeResourceRecordSets request.

Attachments

To access additional content that is associated with this document, unzip the following file: attachment.zip