Why turn it on
DNS problems in a VPC are hard to see. An application says "can't connect", a private endpoint resolves to a public IP, or a security team asks which instance looked up a suspicious domain. Without logs you are guessing.
Route 53 Resolver query logging records the DNS queries that workloads in a VPC send to the Amazon-provided resolver (the .2 address, or 169.254.169.253). Each record includes the name, type, response code, answers and the source IP and instance.
What it does not capture:
- Queries sent straight to your own DNS servers (for example domain controllers) that never reach the Resolver.
- Lookups answered from the instance's own OS cache.
Step 1: choose a destination
| Destination | Use it for |
|---|---|
| CloudWatch Logs | Troubleshooting, Logs Insights queries, metric filters and alarms |
| S3 | Cheap long-term retention, Athena queries |
| Kinesis Data Firehose | Streaming to a SIEM |
Create a log group with a retention period so the logs don't grow forever:
aws logs create-log-group --log-group-name /dns/vpc-prod
aws logs put-retention-policy --log-group-name /dns/vpc-prod --retention-in-days 30
Step 2: create the query logging configuration
LG_ARN=$(aws logs describe-log-groups --log-group-name-prefix /dns/vpc-prod \
--query "logGroups[0].arn" --output text | sed 's/:\*$//')
aws route53resolver create-resolver-query-log-config \
--name vpc-prod-dns \
--destination-arn "$LG_ARN" \
--creator-request-id "vpc-prod-dns-$(date +%s)"
Note the Id (starts with rqlc-) in the output.
Tip
When you set this up in the console, AWS adds the CloudWatch Logs resource policy that lets the service deliver logs. If you script it and no records arrive, check the log group's resource policy (
aws logs describe-resource-policies) before anything else.
Step 3: associate VPCs
aws route53resolver associate-resolver-query-log-config \
--resolver-query-log-config-id rqlc-0123456789abcdef \
--resource-id vpc-0a1b2c3d4e5f
One configuration can be associated with many VPCs in the same Region. Check what is attached:
aws route53resolver list-resolver-query-log-config-associations \
--query "ResolverQueryLogConfigAssociations[].{vpc:ResourceId,status:Status}" --output table
The status should go to ACTIVE. Logs appear within a few minutes of the next queries.
To share one configuration with other accounts, use AWS RAM; the owning account controls the destination.
Step 4: read the logs
A record looks like this (fields trimmed):
{
"version": "1.100000",
"account_id": "111122223333",
"region": "eu-west-1",
"vpc_id": "vpc-0a1b2c3d4e5f",
"query_timestamp": "2026-10-27T09:14:02Z",
"query_name": "mydb.privatelink.database.windows.net.",
"query_type": "A",
"rcode": "NXDOMAIN",
"answers": [],
"srcaddr": "10.20.1.45",
"srcids": { "instance": "i-0abc1234def567890" }
}
Logs Insights queries to keep handy (run them against /dns/vpc-prod):
Names failing to resolve:
fields query_name, rcode
| filter rcode != "NOERROR"
| stats count(*) as failures by query_name, rcode
| sort failures desc
| limit 25
Who is asking for a given name:
fields @timestamp, srcaddr, srcids.instance, query_type, rcode
| filter query_name like /mydb\.privatelink/
| sort @timestamp desc
| limit 50
Top talkers (a script in a tight loop shows up here):
stats count(*) as queries by srcaddr, srcids.instance
| sort queries desc
| limit 20
What a private name actually resolved to (spot a public IP where you expected a private endpoint):
fields @timestamp, query_name, answers.0.Rdata as first_answer
| filter query_name like /blob\.core\.windows\.net|amazonaws\.com/
| sort @timestamp desc
| limit 50
Common findings
| What you see | Usual cause | Fix |
|---|---|---|
Steady NXDOMAIN for *.ec2.internal or a corporate suffix | Wrong DNS search suffix on hosts | Fix the DHCP options set or OS resolver config |
NXDOMAIN for an on-premises zone | Missing or unshared Resolver forwarding rule | Add the rule and associate it with the VPC |
| Private endpoint name returns a public IP | Private hosted zone not associated with this VPC | Associate the zone with the VPC |
SERVFAIL bursts | Outbound endpoint can't reach on-prem DNS, or upstream DNS is down | Check outbound endpoint security groups and on-prem DNS health |
| Thousands of identical queries per minute from one host | Application not caching DNS | Fix the app, or run a local caching resolver |
Turning it off
aws route53resolver disassociate-resolver-query-log-config \
--resolver-query-log-config-id rqlc-0123456789abcdef \
--resource-id vpc-0a1b2c3d4e5f
Prevent it next time
- Enable query logging in your VPC baseline (Terraform or CloudFormation) for every production VPC, not after an incident.
- Add a metric filter on
rcode = "SERVFAIL"and alarm on a sudden rise. - Keep 30 days in CloudWatch for troubleshooting and send a copy to S3 if security needs longer.
- DNS logs are billed as log ingestion; check the current CloudWatch pricing for your volume before turning it on for chatty VPCs.