Goal
You want DNS egress filtering in every workload VPC. That means blocking known-bad and algorithmically generated domains, and in locked-down VPCs, allowing only an explicit list of domains. A bad DNS Firewall rule breaks every service in the VPC at the same time, and the symptoms look like a DNS outage rather than a security control. Work through this checklist in order.
1. Prerequisites
- Resolver query logging is on for every target VPC and goes to CloudWatch Logs or S3. You need it to see what each rule would hit.
- Workloads use the Route 53 Resolver (the VPC
.2address, or169.254.169.253). DNS Firewall only inspects queries that go through it. - You know which VPCs use custom DNS servers in DHCP options, or self-hosted resolvers that forward somewhere other than the Route 53 Resolver. Those queries bypass DNS Firewall.
aws route53resolver list-resolver-query-log-config-associations \
--query "ResolverQueryLogConfigAssociations[].{Vpc:ResourceId,Status:Status,Config:ResolverQueryLogConfigId}"
aws ec2 describe-vpcs --query "Vpcs[].{Vpc:VpcId,Dhcp:DhcpOptionsId}" --output table
2. Build domain lists
- Use the AWS managed lists for threat intelligence, and keep your own lists for allow and block entries.
aws route53resolver list-firewall-domain-lists \
--query "FirewallDomainLists[].{Name:Name,Id:Id,Owner:ManagedOwnerName}" --output table
- Create custom lists. Load large lists from a file in S3 instead of the command line:
aws route53resolver create-firewall-domain-list --creator-request-id corp-allow-1 --name corp-allow
aws route53resolver import-firewall-domains --firewall-domain-list-id rslvr-fdl-0abc \
--operation REPLACE --domain-file-url s3://dns-fw-config/corp-allow.txt
aws route53resolver update-firewall-domains --firewall-domain-list-id rslvr-fdl-0abc \
--operation ADD --domains "*.amazonaws.com" "amazonaws.com" "*.github.com"
- Understand how matching works.
example.commatches only that exact name.*.example.commatches subdomains but not the apex. Add both when you need both.
3. Start in ALERT mode
- Create every new rule with
--action ALERTfirst:
aws route53resolver create-firewall-rule-group --creator-request-id egress-rg-1 --name egress-baseline
aws route53resolver create-firewall-rule --creator-request-id r-threat-1 \
--firewall-rule-group-id rslvr-frg-0abc --firewall-domain-list-id rslvr-fdl-managed-aggregate \
--priority 100 --action ALERT --name alert-aggregate-threats
aws route53resolver create-firewall-rule --creator-request-id r-tunnel-1 \
--firewall-rule-group-id rslvr-frg-0abc --priority 200 --action ALERT \
--dns-threat-protection DNS_TUNNELING --confidence-threshold HIGH --name alert-dns-tunneling
--dns-threat-protection (DNS Firewall Advanced) takes the place of a domain list. Accepted threat types and confidence values depend on your CLI version, so check aws route53resolver create-firewall-rule help on your build.
- Associate the group with a small set of VPCs and watch what matches for at least one full business cycle, including month-end batch jobs:
fields @timestamp, query_name, query_type, srcids.instance, firewall_rule_action, firewall_domain_list_id
| filter ispresent(firewall_rule_action)
| stats count(*) as hits by query_name, firewall_domain_list_id, srcids.instance
| sort hits desc
| limit 100
- Look into every alert hit for a production host. If a managed-list match turns out to be a legitimate dependency, it goes on your allow list, above the block rules in priority.
4. Order rules and rule groups
- Inside a rule group, the lowest priority number is evaluated first, and the first match ends evaluation.
- Across rule groups, the association priority sets the order. Priorities run from 100 to 9900. Firewall Manager uses the values outside that range for its first and last groups.
- For a walled garden, put ALLOW rules for your domain lists at low numbers, then a final BLOCK rule on a list containing
*:
| Priority | Rule | Action |
|---|---|---|
| 100 | corp-allow (AWS service endpoints, package mirrors, SaaS) | ALLOW |
| 200 | Internal zones (*.corp.internal, private hosted zones) | ALLOW |
| 300 | AWS managed threat lists | BLOCK |
| 9000 | * (everything else) | BLOCK or ALERT |
Caution
A walled garden blocks any name you haven't listed, including AWS service endpoints (
*.amazonaws.com,*.api.aws), SSM, ECR, STS, EKS, OS package repositories and time sources. Run the*rule in ALERT mode until the query log shows no unlisted names from production hosts for several days.
- Decide how to handle CNAME and DNAME chains. By default DNS Firewall also checks the domains in a redirection chain. If your allow list holds only the first name but the CDN answers with a CNAME to a domain you haven't listed, the query is blocked. Either list the target domains or set
--firewall-domain-redirection-action TRUST_REDIRECTION_DOMAINon that ALLOW rule.
5. Switch to BLOCK
- Choose the block response:
--block-response | Client sees | Use when |
|---|---|---|
NODATA | Name exists but has no records | Quiet failure; clients usually don't retry aggressively |
NXDOMAIN | Name does not exist | You want clear failures in app logs |
OVERRIDE | A CNAME you choose | Send users to a sinkhole or warning page |
aws route53resolver update-firewall-rule --firewall-rule-group-id rslvr-frg-0abc \
--firewall-domain-list-id rslvr-fdl-managed-aggregate \
--action BLOCK --block-response NXDOMAIN
- Change one rule at a time and watch the query logs and application error rates after each one.
6. Associate with mutation protection
aws route53resolver associate-firewall-rule-group --creator-request-id assoc-vpc-a \
--firewall-rule-group-id rslvr-frg-0abc --vpc-id vpc-0aaa --priority 500 \
--name egress-baseline-vpc-a --mutation-protection ENABLED
Mutation protection stops anyone from removing or reprioritising the association from inside the workload account.
7. Fail-open: decide on purpose
If DNS Firewall can't evaluate queries, the VPC's firewall config decides what happens:
aws route53resolver get-firewall-config --resource-id vpc-0aaa
aws route53resolver update-firewall-config --resource-id vpc-0aaa --firewall-fail-open ENABLED
| Setting | Effect | Choose for |
|---|---|---|
ENABLED (fail open) | Queries resolve without filtering | Availability-first production VPCs |
DISABLED (fail closed) | Queries are blocked | Regulated or high-security VPCs that accept the outage risk |
USE_LOCAL_RESOURCE_SETTING | Use the local default | Accept only if you've checked what that default is |
Warning
Fail-closed turns a DNS Firewall service problem into a full outage of every workload in the VPC. Write the decision down, and get it approved by whoever owns availability.
8. Close bypass paths
- Block outbound UDP and TCP 53 to anything other than the VPC resolver, using security groups, NACLs or AWS Network Firewall.
- Block known DNS-over-HTTPS and DNS-over-TLS (TCP 853) endpoints at the network firewall. Otherwise browsers and malware can skip the Resolver.
- Make sure self-hosted DNS servers forward to the VPC resolver, so their queries get filtered too.
9. Scale across the organisation
- Deploy rule groups with a Firewall Manager DNS Firewall policy so new VPCs pick them up automatically, or share them through AWS RAM and associate them with infrastructure as code.
- Keep allow lists in Git, publish them to S3, and run
import-firewall-domains --operation REPLACEfrom the pipeline. - Alarm on spikes in BLOCK matches per VPC (a metric filter on
firewall_rule_action = "BLOCK"), and route them to both security and on-call.
Prevent it next time
- Put every new rule through ALERT first, in the same pipeline that changes the domain lists.
- Give each workload team a documented way to request allow-list entries with a quick turnaround. Otherwise teams will push for custom DNS servers to get around the firewall.
- Test DNS from each VPC every week with a known-blocked and a known-allowed name. Alarm if the blocked name resolves or the allowed name fails.