Step 1 — Create Hosted Zone in Route 53:
Go to Route 53 → Hosted Zones → Create Hosted Zone Enter your domain e.g. yourcompany.com AWS gives you 4 nameservers like:
ns-123.awsdns-45.com ns-456.awsdns-67.net ns-789.awsdns-89.org ns-012.awsdns-34.co.uk Step 2 — Update Nameservers in GoDaddy:
GoDaddy → My Domains → DNS → Nameservers → Change to Custom Paste those 4 AWS nameservers Done. GoDaddy now just holds the domain name, Route 53 handles all DNS
Step 3 — Add your subdomain A records in Route 53: dev.yourcompany.com → A record → EC2 Dev IP qa.yourcompany.com → A record → EC2 QA IP staging.yourcompany.com → A record → EC2 Staging IP
Complete Step-by-Step Guide — Route 53 Setup
Part 1 — What to Ask Admin (Two Separate Things)
Thing 1 — Add Route 53 Access to Your IAM User Your admin has already given you EC2FullAccess, VPCFullAccess etc. on your user or group. He just needs to add one more managed policy the same way he added those. Tell admin:
"Please attach AmazonRoute53FullAccess managed policy to my IAM user (or the group I am in) — same way you added EC2FullAccess and VPCFullAccess."
Admin steps: AWS Console → IAM → Users → (your username) → Permissions tab → Add Permissions → Attach policies directly → Search: AmazonRoute53FullAccess → Tick it → Next → Add Permissions OR if he manages you via a Group: AWS Console → IAM → User Groups → (your group name) → Permissions tab → Add Permissions → Search: AmazonRoute53FullAccess → Tick it → Add Permissions ✅ This is an AWS built-in policy — no JSON needed, admin just searches and selects it.
Thing 2 — Add Inline Policy to Existing Lambda Role Your Lambda role lambda-ec2-start-stop-role already has:
AmazonEC2FullAccess AWSLambdaBasicExecutionRole
Admin just needs to add Route 53 permission to that same role. Tell admin:
"Please add an inline policy to the existing Lambda role lambda-ec2-start-stop-role"
Admin steps: AWS Console → IAM → Roles → Search: lambda-ec2-start-stop-role → Click on it → Permissions tab → Add Permissions → Create Inline Policy → Click JSON tab → Clear existing content, paste this: json{ "Version": "2012-10-17", "Statement": [ { "Sid": "LambdaRoute53Update", "Effect": "Allow", "Action": [ "route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets", "route53:GetChange" ], "Resource": "*" } ] }
→ Next → Policy name: Route53DNSUpdatePolicy → Create Policy
✅ Done. Lambda can now update Route 53 DNS records.
---
### Difference Between the Two Policies — Why Not Same?
This confused you earlier, so let me clarify clearly:
| | Your IAM User | Lambda Role |
|---|---|---|
| **Who uses it** | You, logging into AWS Console | Lambda function running automatically |
| **What it needs** | Full Route 53 access — create zones, manage all records | Only update existing records — nothing else |
| **Policy to use** | `AmazonRoute53FullAccess` (AWS managed) | Small inline policy (only 3 actions) |
| **Why different** | You need to set everything up manually | Lambda only needs to update one A record |
The big JSON with `CreateHostedZone`, `DeleteHostedZone` etc. that I mentioned earlier — **that was for your user, not Lambda.** `AmazonRoute53FullAccess` already covers all of that, so no need to write custom JSON for your user. Admin just selects the managed policy.
---
## Part 2 — You Do This (After Admin Gives Access)
---
### Step 1 — Create Hosted Zone in Route 53
AWS Console → Route 53 → Hosted Zones → Create Hosted Zone → Domain name: yourcompany.com → Type: Public Hosted Zone → Create
AWS will give you **4 nameservers** like:
ns-123.awsdns-12.com ns-456.awsdns-34.net ns-789.awsdns-56.org ns-012.awsdns-78.co.uk
**Copy all 4 — you need these for GoDaddy.**
---
### Step 2 — Update Nameservers in GoDaddy
Ask whoever has GoDaddy login access:
> *"Please change nameservers for `yourcompany.com` to these 4 AWS nameservers"*
GoDaddy → My Products → DNS → Nameservers → Change → Custom → Enter all 4 nameservers from Route 53 → Save
⏱ Propagation takes **10 minutes to a few hours**. After this GoDaddy is done — you never touch it again for subdomains.
---
### Step 3 — Add Your Subdomain A Records in Route 53
Route 53 → Hosted Zones → yourcompany.com → Create Record
Record 1: Name: dev.yourcompany.com Type: A Value: (current IP of your dev EC2) TTL: 60
Record 2: Name: qa.yourcompany.com Type: A Value: (current IP of your QA EC2) TTL: 60
Keep TTL as **60 seconds** — this means when Lambda updates the IP, it will reflect in 60 seconds only.
---
### Step 4 — Add Tag on Each EC2
AWS Console → EC2 → Instances → (your dev instance) → Tags tab → Manage Tags → Add Tag
Key: dns_name Value: dev.yourcompany.com
Do same for QA instance:
Key: dns_name Value: qa.yourcompany.com
Step 5 — Update Your Start Lambda Function Replace your existing start Lambda with this: pythonimport boto3 import time from botocore.config import Config
ec2_config = Config(region_name='us-east-1') ec2 = boto3.client('ec2', config=ec2_config) route53 = boto3.client('route53', region_name='us-east-1')
HOSTED_ZONE_ID = 'YOUR_HOSTED_ZONE_ID' # paste from Route 53 console
def get_instances(): return [ item["Instances"][0] for item in ec2.describe_instances( Filters=[ {'Name': 'instance-state-name', 'Values': ['stopped']}, {'Name': 'tag:AutoSchedule', 'Values': ['true']} ] ).get("Reservations") ]
def start_instances(instance_ids): ec2.start_instances(InstanceIds=instance_ids)
def wait_for_public_ip(instance_id, retries=10, delay=6): for _ in range(retries): response = ec2.describe_instances(InstanceIds=[instance_id]) ip = response['Reservations'][0]['Instances'][0].get('PublicIpAddress') if ip: return ip time.sleep(delay) return None
def update_route53(dns_name, ip): route53.change_resource_record_sets( HostedZoneId=HOSTED_ZONE_ID, ChangeBatch={ 'Changes': [{ 'Action': 'UPSERT', 'ResourceRecordSet': { 'Name': dns_name, 'Type': 'A', 'TTL': 60, 'ResourceRecords': [{'Value': ip}] } }] } ) print(f"Route53 updated: {dns_name} → {ip}")
def lambda_handler(event, context): instances = get_instances() instance_ids = [i["InstanceId"] for i in instances]
print(f"Starting instances: {instance_ids}")
start_instances(instance_ids)
for instance in instances:
instance_id = instance["InstanceId"]
tags = {t['Key']: t['Value'] for t in instance.get('Tags', [])}
dns_name = tags.get('dns_name')
if not dns_name:
print(f"No dns_name tag on {instance_id}, skipping Route53 update")
continue
print(f"Waiting for public IP on {instance_id}...")
ip = wait_for_public_ip(instance_id)
if ip:
update_route53(dns_name, ip)
else:
print(f"Could not get public IP for {instance_id}, skipping")
**One thing to update in code:**
HOSTED_ZONE_ID = 'YOUR_HOSTED_ZONE_ID'
Get this from:
Route 53 → Hosted Zones → yourcompany.com → copy Hosted Zone ID
---
## Full Picture — Everything Together
ADMIN DOES: ├── AmazonRoute53FullAccess → attached to your IAM user/group └── Route53DNSUpdatePolicy → inline policy on lambda-ec2-start-stop-role
YOU DO: ├── Create Hosted Zone in Route 53 ├── Copy 4 nameservers → give to GoDaddy admin to update ├── Add A records in Route 53 for dev, qa subdomains ├── Add dns_name tag on each EC2 instance ├── Update HOSTED_ZONE_ID in Lambda code └── Release Elastic IPs (no longer needed)
AUTOMATIC AFTER THAT: └── EC2 starts → Lambda → Route 53 A record updated → subdomain resolves in 60 seconds
=================================== Safe Migration Checklist
✅ Step 1 — List all existing records from GoDaddy DNS page ✅ Step 2 — Create Hosted Zone in Route 53 ✅ Step 3 — Add ALL same records in Route 53 (copy exactly) ✅ Step 4 — Double check every subdomain is in Route 53 ✅ Step 5 — Now change nameservers in GoDaddy to Route 53 ✅ Step 6 — Wait 10-30 mins for propagation ✅ Step 7 — Test every subdomain still works ✅ Step 8 — Remove Elastic IPs after Lambda is working
import boto3
import time
from botocore.config import Config
ec2_config = Config(region_name='us-east-1')
ec2 = boto3.client('ec2', config=ec2_config)
route53 = boto3.client('route53')
# ---- CHANGE THESE VALUES ----
HOSTED_ZONE_ID = 'YOUR_HOSTED_ZONE_ID'
TTL = 300
DOMAIN_NAMES = [
'example.info', #
]
# ------------------------------
def get_instances():
return [
item["Instances"][0]["InstanceId"]
for item in ec2.describe_instances(
Filters=[
{'Name': 'instance-state-name', 'Values': ['stopped']},
{'Name': 'tag:AutoSchedule', 'Values': ['true']}
]
).get("Reservations")
]
def start_instances(instance_ids):
ec2.start_instances(InstanceIds=instance_ids)
def wait_for_public_ip(instance_id, retries=10, delay=10):
"""Wait until EC2 instance gets a public IP after starting"""
for i in range(retries):
response = ec2.describe_instances(InstanceIds=[instance_id])
public_ip = response['Reservations'][0]['Instances'][0].get('PublicIpAddress')
if public_ip:
print(f"Got public IP: {public_ip}")
return public_ip
print(f"Waiting for IP... attempt {i+1}/{retries}")
time.sleep(delay)
raise Exception(f"Could not get public IP for instance {instance_id}")
def update_route53(public_ip):
"""Update ALL domains/subdomains in Route 53 with new EC2 public IP"""
changes = [
{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': domain,
'Type': 'A',
'TTL': TTL,
'ResourceRecords': [{'Value': public_ip}]
}
}
for domain in DOMAIN_NAMES
]
response = route53.change_resource_record_sets(
HostedZoneId=HOSTED_ZONE_ID,
ChangeBatch={
'Comment': 'Auto updated by Lambda on EC2 start',
'Changes': changes
}
)
print(f"Route 53 updated: {response['ChangeInfo']['Status']}")
for domain in DOMAIN_NAMES:
print(f" → {domain} = {public_ip}")
def lambda_handler(event, context):
instances = get_instances()
if not instances:
print("No instances found to start")
return
print(f"Starting the instances {instances}")
start_instances(instances)
# Update Route 53 for each instance
for instance_id in instances:
try:
public_ip = wait_for_public_ip(instance_id)
update_route53(public_ip)
print(f"DNS updated for {instance_id} → {public_ip}")
except Exception as e:
print(f"ERROR for {instance_id}: {str(e)}")
raise
Parallal
import boto3
import time
import concurrent.futures
from botocore.config import Config
ec2_config = Config(region_name='us-east-1')
ec2 = boto3.client('ec2', config=ec2_config)
route53 = boto3.client('route53')
# ---- CHANGE THESE VALUES ----
HOSTED_ZONE_ID = 'YOUR_HOSTED_ZONE_ID'
TTL = 300
DOMAIN_NAMES = [
'example.info',
]
# ------------------------------
def get_instances():
return [
item["Instances"][0]["InstanceId"]
for item in ec2.describe_instances(
Filters=[
{'Name': 'instance-state-name', 'Values': ['stopped']},
{'Name': 'tag:AutoSchedule', 'Values': ['true']}
]
).get("Reservations")
]
def start_instances(instance_ids):
ec2.start_instances(InstanceIds=instance_ids)
def wait_for_public_ip(instance_id, retries=10, delay=10):
"""Wait until EC2 instance gets a public IP after starting"""
for i in range(retries):
response = ec2.describe_instances(InstanceIds=[instance_id])
public_ip = response['Reservations'][0]['Instances'][0].get('PublicIpAddress')
if public_ip:
print(f"Got public IP for {instance_id}: {public_ip}")
return public_ip
print(f"Waiting for IP {instance_id}... attempt {i+1}/{retries}")
time.sleep(delay)
raise Exception(f"Could not get public IP for instance {instance_id}")
def update_route53(public_ip):
"""Update ALL domains in Route 53 with new EC2 public IP"""
changes = [
{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': domain,
'Type': 'A',
'TTL': TTL,
'ResourceRecords': [{'Value': public_ip}]
}
}
for domain in DOMAIN_NAMES
]
response = route53.change_resource_record_sets(
HostedZoneId=HOSTED_ZONE_ID,
ChangeBatch={
'Comment': 'Auto updated by Lambda on EC2 start',
'Changes': changes
}
)
print(f"Route 53 updated: {response['ChangeInfo']['Status']}")
for domain in DOMAIN_NAMES:
print(f" → {domain} = {public_ip}")
def process_instance(instance_id):
"""Wait for IP and update Route 53 for a single instance"""
try:
public_ip = wait_for_public_ip(instance_id)
update_route53(public_ip)
print(f"✅ Done: {instance_id} → {public_ip}")
except Exception as e:
print(f"❌ ERROR for {instance_id}: {str(e)}")
def lambda_handler(event, context):
instances = get_instances()
if not instances:
print("No instances found to start")
return
print(f"Starting {len(instances)} instances: {instances}")
start_instances(instances)
# Process all instances in PARALLEL
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
executor.map(process_instance, instances)
print("All instances processed!")