Keeping DNS zones organized with zone delegation
DNS is one of those things that works so well in the background that nobody thinks about it until someone accidentally deletes an A record five minutes before a demo. As infrastructure grows and more environments show up, dumping every record for every environment into one giant hosted zone starts to feel a lot like that moment, just waiting to happen. Zone delegation is the fix, and it's simpler to set up than most people expect.
Organizing hosted zones in separate environments
Instead of having a single hosted zone holding records of all environments
mixed together, each environment gets its own hosted zone, and the parent zone
just points to them. So example.com stays the parent,
while dev.example.com and others become their own
independent zones that happen to live under the same domain.
This isn't just tidiness for the sake of tidiness. A smaller blast radius is
the biggest win here: a bad record, a misconfigured TTL or someone
fat-fingering a change in the dev zone can't touch
anything in prod, because they're literally different zones with different
record sets. It also makes IAM a lot more sane, since you can grant a team or
a CI pipeline access to the dev zone alone, without
handing over the keys to production DNS.
There's also a practical side for anyone using infrastructure as code. Since separate zones usually mean separate state, changes to one environment don't require locking or touching the state of another. And when something does go wrong, the change history for a single small zone is a lot easier to reason about than digging through a zone with hundreds of unrelated records.
Delegating a zone with Route53
Delegation itself is done through NS records. The parent zone doesn't need to know anything about the records inside the child zone, it just needs to know which name servers are authoritative for that subdomain and point to them with an NS record.
Creating the parent and child hosted zones
resource "aws_route53_zone" "parent" {
name = "example.com"
}
resource "aws_route53_zone" "dev" {
name = "dev.example.com"
}
Route53 automatically assigns a set of name servers to the new
dev zone the moment it's created, you just need to grab
them and hand them over to the parent.
Pointing the parent to the child through NS records
resource "aws_route53_record" "dev_delegation" {
zone_id = aws_route53_zone.parent.zone_id
name = "dev.example.com"
type = "NS"
ttl = 172800
records = aws_route53_zone.dev.name_servers
}
Once that applies, anything under dev.example.com gets
resolved by asking the parent zone, which says "not my problem, go ask these
name servers instead", and the child zone takes it from there. Any record you
create inside the dev zone from that point on is
completely isolated from the parent and from every other environment's zone.
Confirming the delegation worked
dig NS dev.example.com +short
If the output matches the name servers from the child zone, the delegation is working and you can move on with your life.
Wrap-up
Splitting hosted zones by environment and delegating them properly is a small
amount of work that pays off the first time someone needs isolated
permissions, a cleaner audit trail, or just wants to nuke and recreate a
dev
zone without holding their breath. It's one of those changes that feels
unnecessary until the day it very much isn't.