Back to Articles

Route53-Transfer: DNS Migration Without the AWS Console Circus

[ View on GitHub ]

Route53-Transfer: DNS Migration Without the AWS Console Circus

Hook

Most developers think migrating DNS zones between AWS accounts requires clicking through the console for hours or writing custom boto scripts. A 500-line Python tool proves otherwise.

Context

Route53 is AWS's managed DNS service, and while it's reliable, it lacks native export/import functionality for moving zones between accounts or creating human-readable backups. The AWS console forces you to manually recreate records one by one. The official AWS CLI requires separate API calls for each record type, making bulk operations tedious and error-prone. Infrastructure-as-code tools like Terraform solve this elegantly for greenfield projects, but what about existing zones with hundreds of records that predate your IaC adoption?

This gap becomes painful during common scenarios: migrating DNS from a contractor's AWS account to your own, creating disaster recovery snapshots that non-DevOps team members can read, consolidating multiple AWS organizations after an acquisition, or simply version-controlling your DNS configuration without committing to a full IaC migration. The route53-transfer tool emerged to solve exactly this problem—providing a lightweight, scriptable way to serialize Route53 zones to CSV and restore them elsewhere.

Technical Insight

Destination AWS Account

Local Environment

Source AWS Account

dump command

list_resource_record_sets

DNS records JSON

serialize

load command

parse records

change_resource_record_sets

CLI Interface

dump/load commands

Route53 API

Hosted Zone

Boto SDK

AWS Client

CSV File

DNS Records

Route53 API

Hosted Zone

System architecture — auto-generated

At its core, route53-transfer is a Python CLI wrapper around the boto AWS SDK that bridges the gap between Route53's API and human-readable CSV files. The architecture is deliberately simple: two primary operations (dump and load) that serialize DNS records to and from a flat file format.

The dump operation connects to Route53, enumerates all resource record sets in a specified hosted zone, and writes them to CSV with columns for name, type, TTL, and values. Here's what a typical backup looks like:

# Dump a zone to CSV using AWS profile credentials
python route53-transfer.py dump \
  --zone-id Z1234567890ABC \
  --output my-domain-backup.csv \
  --profile prod-account

# The resulting CSV is human-readable:
# name,type,ttl,values
# example.com.,A,300,"192.0.2.1"
# www.example.com.,CNAME,300,"example.com."
# example.com.,MX,3600,"10 mail.example.com."

The load operation reverses this process, parsing the CSV and creating records via the Route53 API. Critically, it supports specifying different AWS credentials for source and destination, enabling cross-account transfers:

# Restore to a different AWS account
python route53-transfer.py load \
  --zone-id Z0987654321XYZ \
  --input my-domain-backup.csv \
  --profile new-account

What makes this approach powerful is the CSV intermediate format. Unlike JSON or YAML, CSV files are trivial to diff in version control, easy to edit in spreadsheets for bulk changes, and readable by non-technical stakeholders. Need to update 50 A records pointing to an old IP? Open the CSV in Excel, find-and-replace, then reload.

The tool handles private hosted zones differently than public ones, requiring VPC association details during restoration. When dumping a private zone, you'll need to note the VPC ID separately, then specify it during the load operation. This design choice reflects Route53's API structure, where VPC associations are zone-level metadata rather than record-level attributes.

One architectural decision worth noting: route53-transfer uses the original boto library rather than the newer boto3. While this creates maintenance concerns (boto is deprecated), it also means the codebase remains compact and focuses on stable Route53 APIs that haven't changed significantly. The tool doesn't attempt to support every Route53 feature—no alias records for AWS resources, no health checks, no traffic policies—just core DNS record types (A, AAAA, CNAME, MX, TXT, SRV, NS, PTR).

For automation pipelines, the CSV format integrates cleanly with shell scripts and CI/CD workflows:

#!/bin/bash
# Daily DNS backup script
DATE=$(date +%Y-%m-%d)
ZONE_ID="Z1234567890ABC"
BACKUP_DIR="/backups/dns"

python route53-transfer.py dump \
  --zone-id $ZONE_ID \
  --output "$BACKUP_DIR/zone-$DATE.csv"

# Commit to git for version history
cd $BACKUP_DIR
git add .
git commit -m "DNS backup $DATE"
git push

This creates an audit trail of DNS changes without requiring a full IaC migration. When incidents happen—someone accidentally deletes records, a vendor makes unauthorized changes—you have timestamped snapshots to restore from or diff against.

Gotcha

The most significant limitation is the dependency on the deprecated boto library instead of boto3. AWS stopped maintaining boto years ago, meaning newer Route53 features like DNSSEC, CIDR collections, or geoproximity routing won't work. If your zones use these advanced features, the tool will silently skip them during dumps, potentially creating incomplete backups. There's no validation to warn you about missing records.

The CSV format, while human-friendly, becomes unwieldy with large zones. A zone with 10,000 records creates a massive CSV that's slow to parse and easy to corrupt with manual edits. There's no incremental sync capability—every operation is a full dump or load. If you need to sync just the changes between two zones, you'll need to write custom diffing logic. The tool also lacks dry-run functionality, so there's no safe way to preview what records will be created or modified before executing a load operation. In production environments, this means you're flying blind until records are already changed. Alias records—Route53's special record type for pointing to other AWS resources like CloudFront distributions or ELBs—aren't fully supported, which limits usefulness for modern AWS architectures that rely heavily on these integrations.

Verdict

Use if: You need to migrate existing DNS zones between AWS accounts once or infrequently, want human-readable DNS backups for disaster recovery without IaC overhead, need to version-control DNS changes in a format non-DevOps team members can review, or are working with straightforward zones using basic record types (A, CNAME, MX, TXT). Skip if: Your zones use modern Route53 features like DNSSEC or alias records, you need incremental sync between zones rather than full dumps, you're managing very large zones with thousands of records where CSV becomes impractical, or you want actively maintained dependencies and long-term production support. For those scenarios, invest in Terraform with the AWS provider or use cli53 for a more robust CLI experience with active maintenance.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/cosmin-route53-transfer.svg)](https://starlog.is/api/badge-click/developer-tools/cosmin-route53-transfer)