Taming Outbound DNS: Using AWS Route 53 DNS Firewall to Control and Audit What Your Services…
Taming Outbound DNS: Using AWS Route 53 DNS Firewall to Control and Audit What Your Services Resolve
by Gary Worthington, More Than Monkeys
Why DNS Control Matters More Than You Think
When you lock down outbound traffic in AWS, you quickly discover how slippery DNS can be.
A Lambda, ECS task or EC2 instance might not have direct internet egress, but if it can still resolve evil.com through your resolver, you’ve got a blind spot.
That’s where AWS Route 53 DNS Firewall comes in. It gives you a managed way to filter and log DNS queries made through your Route 53 Resolver endpoints; so you can allow known domains, block risky ones, and record every lookup for auditing.
This is particularly useful when:
- You want to ensure all DNS traffic goes through a single resolver (for inspection or compliance).
- You need to detect hard-coded IPs or unapproved domains.
- You’re validating that your apps only talk to known services (e.g. S3, DynamoDB, or a trusted SaaS vendor).
How It Works
At a high level:
- You create inbound and outbound Resolver endpoints in your VPC.
- You attach rule groups to your VPC that define allowed or blocked domains.
- You turn on query logging so every lookup is sent to CloudWatch Logs, S3, or Kinesis Firehose.
Once configured, every DNS query made by workloads in that VPC passes through the firewall.
[VPC Workload] → [Route 53 Resolver DNS Firewall] → [Internet DNS]
│
└─► [CloudWatch Logs / S3 / Kinesis]
Defining Rules
You can build your own domain lists or use AWS-managed ones.
Examples:
- Allow only approved services (amazonaws.com, .yourcompany.com).
- Block known bad domains (malware, phishing).
- Catch everything else with a final BLOCK rule and a default-deny stance.
Here’s a more complete Terraform example that does all three:
resource "aws_route53_resolver_firewall_rule_group" "main" {
name = "dnsfw-demo-rules"
}
# Allow known good domains
resource "aws_route53_resolver_firewall_domain_list" "allowlist" {
name = "dnsfw-allowlist"
domains = ["amazonaws.com", "morethanmonkeys.co.uk"]
}
resource "aws_route53_resolver_firewall_rule" "allow" {
action = "ALLOW"
firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.main.id
firewall_domain_list_id = aws_route53_resolver_firewall_domain_list.allowlist.id
priority = 100
name = "allow-approved-domains"
}
# Optionally block known bad domains
resource "aws_route53_resolver_firewall_domain_list" "blocklist" {
name = "dnsfw-blocklist"
domains = ["malicious.example.com", "crypto-miner.net"]
}
resource "aws_route53_resolver_firewall_rule" "block_known_bad" {
action = "BLOCK"
block_response = "NODATA"
firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.main.id
firewall_domain_list_id = aws_route53_resolver_firewall_domain_list.blocklist.id
priority = 150
name = "block-known-bad-domains"
}
# or, Explicit catch-all block (default-deny)
resource "aws_route53_resolver_firewall_domain_list" "block_all" {
name = "dnsfw-block-all"
domains = ["*"]
}
resource "aws_route53_resolver_firewall_rule" "block_all" {
action = "BLOCK"
block_response = "NODATA"
firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.main.id
firewall_domain_list_id = aws_route53_resolver_firewall_domain_list.block_all.id
priority = 200
name = "block-everything-else"
}
# Attach to VPC
resource "aws_route53_resolver_firewall_rule_group_association" "vpc" {
name = "dnsfw-association"
firewall_rule_group_id = aws_route53_resolver_firewall_rule_group.main.id
vpc_id = aws_vpc.main.id
priority = 100
}This configuration enforces a default-deny policy:
Only explicitly allowed domains can resolve, specific known-bad ones are blocked immediately, and everything else returns NODATA.
That’s the difference between “some DNS filtering” and a controlled network boundary.
Logging and Visibility
You can enable query logging to CloudWatch:
resource "aws_route53_resolver_query_log_config" "logs" {
name = "dnsfw-logs"
destination_arn = aws_cloudwatch_log_group.dnsfw.arn
}
resource "aws_cloudwatch_log_group" "dnsfw" {
name = "/aws/route53/dnsfw-demo"
retention_in_days = 30
}
resource "aws_route53_resolver_query_log_config_association" "vpc" {
resolver_query_log_config_id = aws_route53_resolver_query_log_config.logs.id
resource_id = aws_vpc.main.id
}You’ll then see entries like:
{
"query_name": "api.openai.com.",
"query_type": "A",
"response_code": "NOERROR",
"vpc_id": "vpc-0abc1234",
"timestamp": "2025-10-09T15:24:00Z"
}From here, you can filter or aggregate logs in CloudWatch Insights, e.g.:
fields query_name
| stats count() by query_name
| sort count() desc
This gives you instant visibility into what your workloads are resolving.
Detecting Hard-coded IPs
One of the most powerful patterns I’ve used is cross-checking DNS logs against VPC Flow Logs.
If an IP appears in flow logs but never shows up in DNS logs, it’s likely your code (or a library) is connecting directly to a hard-coded address — bypassing resolution entirely.
You can automate this in a Lambda that runs every few minutes:
- Query CloudWatch Logs for recent DNS queries.
- Query VPC Flow Logs for outbound connections.
- Diff the two sets of IPs.
- Attempt a reverse lookup to identify unexpected destinations.
Defending Against Supply Chain Attacks
The recent supply chain poisoning attacks against npm serve as a reminder that not every threat comes through open ports.
A malicious dependency might attempt to exfiltrate data or beacon home using DNS, often through obscure domains that never touch your allowlists.
By forcing all DNS queries through Route 53 DNS Firewall and logging them centrally, you can:
- Detect packages that attempt to resolve unexpected domains.
- Block resolution of newly registered or untrusted domains entirely.
- Alert when code tries to contact destinations outside your approved zones.
In practice, this means even if a compromised npm package is pulled into your Lambda layer or container image, its first outbound DNS query gives it away.
You gain a forensic trail and, ideally, a blocked resolution before any connection succeeds.
It’s not a silver bullet, but it’s a meaningful layer in a broader defence-in-depth strategy that includes dependency scanning and egress controls.
DNS Firewall vs Network Firewall
It’s worth noting that Route 53 DNS Firewall is primarily a visibility and governance tool. It helps you control what domains are resolved, but it doesn’t stop workloads from connecting to IPs directly.
If you can afford the extra cost and complexity, AWS Network Firewall (or a proxy such as Squid) is a stronger solution.
Network Firewall can inspect, filter, and block traffic at the network and transport layers, not just DNS. You can define stateful rules, restrict ports, or route outbound traffic through an inspection VPC.
A Squid proxy offers similar control, particularly if you need to whitelist destinations by URL rather than by domain, or if you’re dealing with HTTP/S traffic that must be logged and filtered.
For small to mid-size environments, DNS Firewall provides great value for minimal setup.
But for high-assurance or regulated workloads, you’ll want both: DNS Firewall for visibility and governance, and Network Firewall (or Squid) for true enforcement.
Cost and Gotchas
Costs are modest (a few pence per million queries plus CloudWatch storage) but the benefits are huge if you’re trying to secure multi-tenant workloads or handle regulated data (e.g. NHS).
The main gotchas:
- DNS Firewall only applies if workloads use Route 53 Resolver; custom DNS servers or cached resolutions bypass it.
- Logging every query can get noisy; filter or aggregate before shipping to S3.
- Managed domain lists update slowly, so maintain your own for critical allow/deny cases.
When You Should Use It
If you:
- Run workloads in private subnets using a NAT or DNS resolver.
- Need assurance that only approved destinations are queried.
- Want forensic logs for DNS traffic during incident response.
- Don’t have budget (yet) for a full-blown Network Firewall or proxy setup.
- Want to mitigate the blast radius of a potential supply chain compromise like the npm attack.
Then Route 53 DNS Firewall should be part of your baseline network setup.
It’s the simplest way to see and shape outbound behaviour without rewriting application code or managing proxy appliances.
Closing Thoughts
DNS is one of those invisible layers that quietly undermines security if you ignore it.
Adding a firewall to it gives you back control, visibility, and confidence that your workloads are doing what you think they’re doing.
In a world where open-source packages can be weaponised overnight, visibility is everything.
DNS Firewall won’t stop every threat, but it gives you a fighting chance to spot the odd one out, before it spots you.
Gary Worthington is a software engineer, delivery consultant, and fractional CTO who helps teams move fast, learn faster, and scale when it matters. He writes about modern engineering, product thinking, and helping teams ship things that matter.
Through his consultancy, More Than Monkeys, Gary helps startups and scaleups improve how they build software — from tech strategy and agile delivery to product validation and team development.
Visit morethanmonkeys.co.uk to learn how we can help you build better, faster.
Follow Gary on LinkedIn for practical insights into engineering leadership, agile delivery, and team performance.