Skip to content

Instantly share code, notes, and snippets.

@swateek
Created August 14, 2025 04:07
Show Gist options
  • Save swateek/1a6c1367ebce3cd8a9f6a432c5c4fb5f to your computer and use it in GitHub Desktop.
Save swateek/1a6c1367ebce3cd8a9f6a432c5c4fb5f to your computer and use it in GitHub Desktop.
AWS Route53 Management Script
#!/usr/bin/env bash
set -euo pipefail
AWS_PROFILE="devops"
function hosted_zone_exists() {
local domain=$1
aws route53 list-hosted-zones-by-name \
--dns-name "$domain" \
--query "HostedZones[?Name=='${domain}.'] | length(@)" \
--output text \
--profile "$AWS_PROFILE"
}
function get_zone_id() {
local domain=$1
aws route53 list-hosted-zones-by-name \
--dns-name "$domain" \
--query "HostedZones[?Name=='${domain}.'].Id | [0]" \
--output text \
--profile "$AWS_PROFILE" | sed 's|/hostedzone/||'
}
function create_hosted_zone() {
local domain=$1
echo "Checking if hosted zone exists for $domain..."
if [[ $(hosted_zone_exists "$domain") -eq 1 ]]; then
echo "Hosted zone already exists for $domain. Skipping creation."
return
fi
echo "Creating hosted zone for $domain..."
hosted_zone_id=$(aws route53 create-hosted-zone \
--name "$domain" \
--caller-reference "$(date +%s)" \
--hosted-zone-config Comment="Created via script",PrivateZone=false \
--query "HostedZone.Id" \
--output text \
--profile "$AWS_PROFILE" \
|| { echo "❌ Failed to create hosted zone."; exit 1; })
hosted_zone_id=${hosted_zone_id#/hostedzone/}
echo "✅ Hosted zone created: $hosted_zone_id"
if [[ "$domain" == *.*.* ]]; then
add_ns_to_parent "$domain" "$hosted_zone_id"
else
echo "ℹ $domain is not a subdomain, skipping parent NS record update."
fi
}
function delete_hosted_zone() {
local domain=$1
if [[ $(hosted_zone_exists "$domain") -eq 0 ]]; then
echo "No hosted zone found for $domain. Nothing to delete."
return
fi
zone_id=$(get_zone_id "$domain")
echo "Deleting all records (except SOA/NS) from zone $zone_id..."
tmpfile=$(mktemp)
aws route53 list-resource-record-sets \
--hosted-zone-id "$zone_id" \
--profile "$AWS_PROFILE" \
| jq '{
Changes: [
.ResourceRecordSets[]
| select(.Type != "NS" and .Type != "SOA")
| {Action: "DELETE", ResourceRecordSet: .}
]
}' > "$tmpfile"
if [[ $(jq '.Changes | length' "$tmpfile") -gt 0 ]]; then
aws route53 change-resource-record-sets \
--hosted-zone-id "$zone_id" \
--change-batch file://"$tmpfile" \
--profile "$AWS_PROFILE"
fi
rm "$tmpfile"
if [[ "$domain" == *.*.* ]]; then
remove_ns_from_parent "$domain"
fi
echo "Deleting hosted zone $domain..."
aws route53 delete-hosted-zone \
--id "$zone_id" \
--profile "$AWS_PROFILE" \
|| { echo "❌ Failed to delete hosted zone."; exit 1; }
echo "✅ Hosted zone deleted."
}
function add_ns_to_parent() {
local subdomain=$1
local sub_zone_id=$2
parent_domain="${subdomain#*.}"
parent_zone_id=$(get_zone_id "$parent_domain")
# Get all NS records for the subdomain
ns_values=$(aws route53 list-resource-record-sets \
--hosted-zone-id "$sub_zone_id" \
--query "ResourceRecordSets[?Type=='NS'].ResourceRecords[].Value" \
--output json \
--profile "$AWS_PROFILE" \
| jq '[.[] | {Value: .}]')
if [[ "$parent_zone_id" == "None" ]]; then
echo "⚠ Parent domain $parent_domain not found in Route53."
echo "Please add the following NS records for $subdomain in your DNS provider manually:"
echo "$ns_values" | jq -r '.[].Value'
return
fi
change_batch=$(jq -n \
--arg name "$subdomain." \
--argjson ns "$ns_values" \
'{
Changes: [{
Action: "UPSERT",
ResourceRecordSet: {
Name: $name,
Type: "NS",
TTL: 300,
ResourceRecords: $ns
}
}]
}')
echo "Adding NS record for $subdomain in $parent_domain..."
aws route53 change-resource-record-sets \
--hosted-zone-id "$parent_zone_id" \
--change-batch "$change_batch" \
--profile "$AWS_PROFILE"
echo "✅ NS record added to parent."
}
function remove_ns_from_parent() {
local subdomain=$1
parent_domain="${subdomain#*.}"
parent_zone_id=$(get_zone_id "$parent_domain")
if [[ "$parent_zone_id" == "None" ]]; then
echo "⚠ Parent domain $parent_domain not found in Route53. Please remove NS records manually if needed."
return
fi
echo "Removing NS record for $subdomain from $parent_domain..."
ns_record=$(aws route53 list-resource-record-sets \
--hosted-zone-id "$parent_zone_id" \
--query "ResourceRecordSets[?Name=='${subdomain}.'] | [?Type=='NS']" \
--output json \
--profile "$AWS_PROFILE")
if [[ "$ns_record" == "[]" ]]; then
echo "No NS record found for $subdomain in parent. Skipping."
return
fi
change_batch=$(jq -n --argjson record "$ns_record" '{Changes:[{Action:"DELETE",ResourceRecordSet:$record[0]}]}')
aws route53 change-resource-record-sets \
--hosted-zone-id "$parent_zone_id" \
--change-batch "$change_batch" \
--profile "$AWS_PROFILE"
echo "✅ NS record removed from parent."
}
echo "Enter action (create/delete):"
read -r action
echo "Enter domain (without trailing dot):"
read -r domain
case "$action" in
create) create_hosted_zone "$domain" ;;
delete) delete_hosted_zone "$domain" ;;
*) echo "Invalid action. Use 'create' or 'delete'." ;;
esac
@swateek
Copy link
Author

swateek commented Aug 14, 2025

When to use?

When a new client gets added, we manually entered these credentials via the Console and repeated it; thus automating it via this script

What it needs?

  1. aws-cliv2
  2. an AWS Profile called "devops"

How to Run?

./aws_route53_management.sh

<enter action: create/delete>
<enter Route53 hosted zone name>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment