dig DNS Queries
DNS query tool for record lookups and troubleshooting.
Record Type Queries
# A record - IPv4 address
dig example.com A # Full output
dig +short example.com A # Just the IP
dig +short example.com # A is default
# AAAA record - IPv6 address
dig example.com AAAA
dig +short example.com AAAA
# CNAME - Canonical name (alias)
dig www.example.com CNAME
dig +short www.example.com CNAME # Returns target hostname
# MX - Mail exchangers (priority + hostname)
dig example.com MX
dig +short example.com MX | sort -n # Sorted by priority
# NS - Nameservers for domain
dig example.com NS
dig +short example.com NS
# SOA - Start of Authority (zone metadata)
dig example.com SOA
# Returns: primary-ns admin-email serial refresh retry expire minimum
# TXT - Text records (SPF, DKIM, verification)
dig example.com TXT
dig +short example.com TXT | tr -d '"' # Clean output
# SRV - Service location (AD, Kerberos, LDAP)
dig _kerberos._tcp.inside.domusdigitalis.dev SRV
dig _ldap._tcp.inside.domusdigitalis.dev SRV
dig _gc._tcp.inside.domusdigitalis.dev SRV # Global Catalog
# PTR - Reverse lookup (IP to hostname)
dig -x 10.50.1.20 # Full reverse lookup
dig +short -x 10.50.1.20 # Just hostname
# CAA - Certificate Authority Authorization
dig example.com CAA # Who can issue certs
# TLSA - DANE TLS certificate association
dig _443._tcp.example.com TLSA
# ANY - All records (often blocked by DNS providers)
dig example.com ANY # May return truncated
Output Control
# +short: Just the answer, nothing else
dig +short example.com # One IP per line
dig +short example.com MX # "10 mail.example.com."
# +noall +answer: Clean answer section only
dig +noall +answer example.com # Formatted answer
dig +noall +answer +ttlid example.com # Include TTL
# +multiline: Readable multi-line format (SOA, DNSKEY)
dig +multiline example.com SOA
# +comments: Toggle comment lines
dig +nocomments example.com # Remove ; lines
# +question: Toggle question section
dig +noquestion example.com
# +authority: Toggle authority section
dig +noauthority example.com
# +additional: Toggle additional section
dig +noadditional example.com
# +stats: Toggle query statistics
dig +nostats example.com # No timing info
# +all: Maximum verbosity
dig +all example.com # Everything
# +nocmd: Remove initial command echo
dig +nocmd example.com # No dig command shown
# Combined for scripting (clean, parseable output)
dig +nocmd +noall +answer +ttlid example.com
dig +short +timeout=2 +tries=1 example.com # Fast with timeout
Querying Specific Servers
# @server syntax - query specific DNS server
dig @8.8.8.8 example.com # Google Public DNS
dig @1.1.1.1 example.com # Cloudflare DNS
dig @9.9.9.9 example.com # Quad9
dig @208.67.222.222 example.com # OpenDNS
# Internal DNS servers
dig @10.50.1.90 vault-01.inside.domusdigitalis.dev # bind-01
dig @10.50.1.1 example.com # pfSense forwarder
dig @10.50.1.50 example.com # AD DC (home-dc01)
# Query authoritative nameservers directly
NS=$(dig +short NS example.com | head -1)
dig @"$NS" example.com # Query authoritative
# Compare across servers (consistency check)
for server in 10.50.1.90 10.50.1.1 8.8.8.8 1.1.1.1; do
printf "%-15s: %s\n" "$server" "$(dig +short @$server example.com)"
done
# Query root servers
dig @a.root-servers.net . NS # Root server list
dig @j.root-servers.net com. NS # .com NS from root
Reverse DNS (PTR Lookups)
# -x flag: Automatic reverse zone formatting
dig -x 10.50.1.20 # Converts to 20.1.50.10.in-addr.arpa
dig +short -x 10.50.1.20 # Just hostname
# Manual reverse zone query (same result)
dig 20.1.50.10.in-addr.arpa PTR
# IPv6 reverse lookup
dig -x 2001:4860:4860::8888 # Google DNS IPv6
# Converts to: 8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa
# Reverse DNS sweep (find hostnames in a range)
for i in {1..254}; do
result=$(dig +short -x "10.50.1.$i" 2>/dev/null)
[[ -n "$result" ]] && printf "10.50.1.%-3s -> %s\n" "$i" "$result"
done
# Parallel reverse sweep (faster)
for i in {1..254}; do
(result=$(dig +short -x "10.50.1.$i" 2>/dev/null)
[[ -n "$result" ]] && echo "10.50.1.$i -> $result") &
done | sort -t. -k4 -n
wait
# Verify forward/reverse match (IMPORTANT for PTR validation)
IP="10.50.1.20"
HOSTNAME=$(dig +short -x "$IP")
RESOLVED_IP=$(dig +short "$HOSTNAME")
[[ "$IP" == "$RESOLVED_IP" ]] && echo "PTR valid" || echo "PTR mismatch!"
DNS Tracing and Debugging
# +trace: Follow delegation from root servers
dig +trace example.com # Full delegation path
# Shows: root -> .com NS -> example.com NS -> A record
# +trace with specific record
dig +trace example.com MX
dig +trace _dmarc.example.com TXT
# +nssearch: Query all authoritative nameservers
dig +nssearch example.com # Check NS consistency
# Great for detecting propagation issues
# +identify: Show which server answered
dig +short +identify example.com
# Query statistics
dig example.com | grep -E 'Query time|SERVER|WHEN'
# Response timing comparison
for server in 10.50.1.90 8.8.8.8 1.1.1.1; do
time_ms=$(dig @"$server" +stats example.com 2>/dev/null | awk '/Query time/{print $4}')
printf "%-15s: %s ms\n" "$server" "$time_ms"
done
# TCP mode (for large responses, zone transfers)
dig +tcp example.com # Force TCP
dig +tcp +bufsize=4096 example.com # Larger EDNS buffer
# Timeout and retry control
dig +timeout=2 +tries=3 example.com # 2 sec timeout, 3 attempts
dig +time=1 +retry=1 example.com # Fast fail for scripting
DNSSEC Validation
# +dnssec: Request DNSSEC records
dig +dnssec example.com # Includes RRSIG
dig +dnssec +multiline example.com DNSKEY # Readable format
# Check if domain is signed
dig +short example.com DNSKEY
# Empty = not signed, output = signed
# RRSIG - Record signature
dig example.com RRSIG # Signatures for records
# DNSKEY - Zone signing keys
dig example.com DNSKEY
# KSK (257) = Key Signing Key, ZSK (256) = Zone Signing Key
# DS - Delegation Signer (in parent zone)
dig example.com DS # Hash of DNSKEY
# NSEC/NSEC3 - Authenticated denial of existence
dig nonexistent.example.com # Returns NSEC/NSEC3
# +cd: Disable DNSSEC checking (compare signed vs unsigned)
dig +cd example.com # Checking Disabled
# +sigchase: Follow signature chain (deprecated in newer dig)
dig +sigchase +topdown example.com
# Validate DNSSEC chain manually
dig +dnssec example.com DNSKEY # Get DNSKEY
dig +dnssec com. DS @a.root-servers.net # Get DS from parent
# Check DNSSEC validation with resolver
dig +short cloudflare.com | head -1 # Should resolve
dig +short dnssec-failed.org 2>/dev/null # Should fail (if validating)
# Trust anchor verification
dig . DNSKEY +dnssec | grep -c "DNSKEY" # Root zone keys
Zone Transfers (AXFR/IXFR)
# AXFR - Full zone transfer (must be allowed by server)
dig @ns1.example.com example.com AXFR
# IXFR - Incremental zone transfer (requires serial number)
dig @ns1.example.com example.com IXFR=2024010100
# Test if zone transfer is allowed (common misconfig)
dig @ns1.example.com example.com AXFR +short
# Zone transfer to file
dig @ns1.example.com example.com AXFR > zone-backup.txt
# Parse zone transfer for specific records
dig @ns1.example.com example.com AXFR | awk '$4=="A"{print $1, $5}'
# Count records by type from zone transfer
dig @ns1.example.com example.com AXFR | awk 'NF>3{print $4}' | sort | uniq -c | sort -rn
# Zone transfer security test (should be denied from external)
# If this works from outside, it's a security issue!
dig @ns1.target.com target.com AXFR
# NOTIFY - zone change notification (usually UDP)
# Used by primary to notify secondaries of updates
# Not directly queryable, but visible in DNS traffic
# Get zone serial (to check sync between primary/secondary)
dig +short SOA example.com | awk '{print $3}'
for ns in $(dig +short NS example.com); do
serial=$(dig +short @"$ns" SOA example.com | awk '{print $3}')
echo "$ns: $serial"
done
Security Recon (Email Records)
# SPF - Sender Policy Framework (who can send email)
dig +short example.com TXT | grep -i spf
# v=spf1 include:_spf.google.com ~all
# Interpret SPF mechanisms:
# +all = pass all (dangerous!)
# -all = fail all not listed (strict)
# ~all = softfail (mark suspicious)
# ?all = neutral (no policy)
# include: = check another domain's SPF
# ip4: = allow specific IPv4
# a: = allow A record IPs
# mx: = allow MX record IPs
# DKIM - DomainKeys Identified Mail
# Selector is required (common: google, selector1, s1, default)
dig +short google._domainkey.example.com TXT
dig +short selector1._domainkey.example.com TXT
dig +short default._domainkey.example.com TXT
# Brute force common selectors
for sel in google selector1 selector2 s1 s2 default mail dkim k1; do
result=$(dig +short ${sel}._domainkey.example.com TXT 2>/dev/null)
[[ -n "$result" ]] && echo "$sel: $result"
done
# DMARC - Domain-based Message Authentication
dig +short _dmarc.example.com TXT
# v=DMARC1; p=reject; rua=mailto:dmarc@example.com
# DMARC policy meanings:
# p=none = monitor only (no action)
# p=quarantine = mark as spam
# p=reject = block email
# Full email security audit
DOMAIN="example.com"
echo "=== SPF ==="
dig +short "$DOMAIN" TXT | grep -i spf
echo "=== DMARC ==="
dig +short "_dmarc.$DOMAIN" TXT
echo "=== MX ==="
dig +short "$DOMAIN" MX | sort -n
echo "=== DKIM (common selectors) ==="
for sel in google selector1 selector2 default; do
res=$(dig +short ${sel}._domainkey.$DOMAIN TXT 2>/dev/null | head -1)
[[ -n "$res" ]] && echo "$sel: ${res:0:60}..."
done
Subdomain Enumeration
# Common subdomain guessing
DOMAIN="example.com"
SUBS="www mail ftp api dev staging admin portal vpn remote git gitlab jenkins"
for sub in $SUBS; do
result=$(dig +short "$sub.$DOMAIN" 2>/dev/null)
[[ -n "$result" ]] && printf "%-20s %s\n" "$sub.$DOMAIN" "$result"
done
# Extended wordlist enumeration
while read -r sub; do
result=$(dig +short +time=1 "$sub.$DOMAIN" 2>/dev/null)
[[ -n "$result" ]] && echo "$sub.$DOMAIN -> $result"
done < /usr/share/wordlists/subdomains.txt
# Parallel subdomain enumeration (faster)
DOMAIN="example.com"
parallel -j 50 "dig +short +time=1 {}.$DOMAIN 2>/dev/null | grep -v '^$' && echo {}.$DOMAIN" \
:::: /usr/share/wordlists/subdomains.txt
# Certificate Transparency subdomain discovery
# Query crt.sh API for issued certificates
curl -s "https://crt.sh/?q=%25.$DOMAIN&output=json" | \
jq -r '.[].name_value' | sort -u
# DNS brute with timeout control
timeout_dig() {
local sub="$1" domain="$2"
result=$(timeout 2 dig +short "$sub.$domain" 2>/dev/null)
[[ -n "$result" ]] && echo "$sub.$domain"
}
export -f timeout_dig
echo -e "www\nmail\napi\ndev\ntest" | \
xargs -P10 -I{} bash -c 'timeout_dig "{}" "example.com"'
# Check for wildcard DNS (*.domain.com resolves)
RANDOM_SUB="$(openssl rand -hex 8)"
WILDCARD=$(dig +short "$RANDOM_SUB.$DOMAIN" 2>/dev/null)
[[ -n "$WILDCARD" ]] && echo "Wildcard DNS detected: $WILDCARD"
Batch Operations
# -f flag: Read domains from file
echo -e "example.com\ngoogle.com\ngithub.com" > /tmp/domains.txt
dig -f /tmp/domains.txt +short
# Batch with specific record type
dig -f /tmp/domains.txt MX +short
# Process domain list with custom formatting
while read -r domain; do
ip=$(dig +short "$domain" | head -1)
printf "%-30s %s\n" "$domain" "${ip:-NXDOMAIN}"
done < /tmp/domains.txt
# Parallel batch lookups
xargs -P10 -I{} dig +short {} < /tmp/domains.txt
# Multi-record batch query
while read -r domain; do
echo "=== $domain ==="
echo "A: $(dig +short $domain A | head -1)"
echo "MX: $(dig +short $domain MX | head -1)"
echo "NS: $(dig +short $domain NS | head -1)"
done < /tmp/domains.txt
# Export to CSV
echo "domain,a_record,mx_record" > /tmp/dns-audit.csv
while read -r domain; do
a=$(dig +short "$domain" A | head -1)
mx=$(dig +short "$domain" MX | head -1)
echo "$domain,$a,$mx" >> /tmp/dns-audit.csv
done < /tmp/domains.txt
# DNS health check script
check_dns_health() {
local domain="$1"
local a_ok mx_ok ns_ok
a_ok=$(dig +short "$domain" A | grep -q '.' && echo "OK" || echo "FAIL")
mx_ok=$(dig +short "$domain" MX | grep -q '.' && echo "OK" || echo "FAIL")
ns_ok=$(dig +short "$domain" NS | grep -q '.' && echo "OK" || echo "FAIL")
printf "%-30s A:%-4s MX:%-4s NS:%-4s\n" "$domain" "$a_ok" "$mx_ok" "$ns_ok"
}
export -f check_dns_health
xargs -P5 -I{} bash -c 'check_dns_health "{}"' < /tmp/domains.txt
Infrastructure DNS Operations
# AD/Kerberos SRV record verification
DOMAIN="inside.domusdigitalis.dev"
echo "=== Kerberos SRV Records ==="
dig +short _kerberos._tcp.$DOMAIN SRV
dig +short _kerberos._udp.$DOMAIN SRV
dig +short _kpasswd._tcp.$DOMAIN SRV
dig +short _kpasswd._udp.$DOMAIN SRV
echo "=== LDAP SRV Records ==="
dig +short _ldap._tcp.$DOMAIN SRV
dig +short _ldap._tcp.dc._msdcs.$DOMAIN SRV
echo "=== Global Catalog ==="
dig +short _gc._tcp.$DOMAIN SRV
dig +short _ldap._tcp.gc._msdcs.$DOMAIN SRV
# Site-specific DC lookup (replace SITENAME)
dig +short _ldap._tcp.SITENAME._sites.dc._msdcs.$DOMAIN SRV
# BIND zone health check
BIND_SERVER="10.50.1.90"
for zone in inside.domusdigitalis.dev 1.50.10.in-addr.arpa; do
echo "=== $zone ==="
dig @$BIND_SERVER +short SOA "$zone"
serial=$(dig @$BIND_SERVER +short SOA "$zone" | awk '{print $3}')
echo "Serial: $serial"
done
# Compare primary/secondary zone serials
check_zone_sync() {
local zone="$1"
for ns in $(dig +short NS "$zone"); do
serial=$(dig +short @"$ns" SOA "$zone" | awk '{print $3}')
printf "%-35s %s\n" "$ns" "$serial"
done
}
check_zone_sync "inside.domusdigitalis.dev"
# DNS round-robin check (load balanced A records)
for i in {1..10}; do
dig +short api.example.com
done | sort | uniq -c
# Check DNS server response times
for server in 10.50.1.90 10.50.1.1 8.8.8.8; do
avg_ms=$(for i in {1..5}; do
dig @"$server" +stats example.com 2>/dev/null | awk '/Query time/{print $4}'
done | awk '{sum+=$1}END{printf "%.1f", sum/NR}')
printf "%-15s %s ms avg\n" "$server" "$avg_ms"
done
Output Parsing with awk/sed
# Extract just IPs from dig output
dig example.com | awk '/^[^;]/ && $4=="A" {print $5}'
# Extract TTL values
dig example.com | awk '/^[^;]/ && $4=="A" {print $2, $5}'
# Parse SOA components
dig +short example.com SOA | awk '{
print "Primary NS: " $1
print "Admin: " $2
print "Serial: " $3
print "Refresh: " $4 "s"
print "Retry: " $5 "s"
print "Expire: " $6 "s"
print "Minimum: " $7 "s"
}'
# MX with priority sorting
dig +short example.com MX | sort -n | awk '{print "Priority " $1 ": " $2}'
# Extract all hostnames from any dig response
dig example.com ANY | grep -oP '[a-z0-9.-]+\.[a-z]{2,}' | sort -u
# Build host inventory from zone transfer
dig @ns1.example.com example.com AXFR | awk '
$4=="A" {print $1 "\t" $5}
$4=="CNAME" {print $1 "\t-> " $5}
' | sort
# Calculate average query time
for i in {1..10}; do
dig example.com +stats 2>/dev/null | awk '/Query time/{print $4}'
done | awk '{sum+=$1; n++} END{print "Average: " sum/n " ms"}'
# DNS record diff between servers
diff <(dig @8.8.8.8 +short example.com | sort) \
<(dig @1.1.1.1 +short example.com | sort)
# Table formatted output
{
echo "HOSTNAME|IP|TTL"
dig example.com | awk '/^[^;]/ && $4=="A" {print $1 "|" $5 "|" $2}'
} | column -t -s'|'
Common Gotchas
# WRONG: Expecting +short to always return something
ip=$(dig +short nonexistent.example.com)
echo "IP: $ip" # Empty, no error
# CORRECT: Check for empty result
ip=$(dig +short example.com)
[[ -z "$ip" ]] && echo "Resolution failed" || echo "IP: $ip"
# WRONG: Not handling multiple A records
ip=$(dig +short example.com) # Could be multiple lines!
# CORRECT: Get first record only
ip=$(dig +short example.com | head -1)
# WRONG: Assuming CNAME returns IP
dig +short www.example.com # Returns CNAME target, not IP!
# CORRECT: Chase CNAME to get IP
dig +short www.example.com A # Both CNAME and A
dig +trace www.example.com # Full resolution path
# WRONG: Using dig without timeout (can hang)
ip=$(dig +short slowdns.example.com) # May hang forever
# CORRECT: Set timeout
ip=$(dig +short +time=2 +tries=1 example.com)
# WRONG: Trusting cached results for debugging
dig example.com # May be stale cache
# CORRECT: Query authoritative directly
ns=$(dig +short NS example.com | head -1)
dig @"$ns" example.com # Fresh from source
# WRONG: Forgetting dig uses /etc/resolv.conf
dig internal.corp.local # May fail if not in search
# CORRECT: Specify internal DNS
dig @10.50.1.90 internal.corp.local
# WRONG: Not URL-encoding in queries (for TXT with special chars)
# CORRECT: TXT records are returned with quotes, handle them
dig +short example.com TXT | tr -d '"'
# WRONG: Zone transfer without checking permissions
dig @ns1.example.com example.com AXFR # Unauthorized attempt!
# Only do this on domains you control or have permission to test
Quick Reference
# Record Types
dig example.com A # IPv4 address
dig example.com AAAA # IPv6 address
dig example.com MX # Mail servers
dig example.com NS # Nameservers
dig example.com TXT # Text records (SPF, DKIM)
dig example.com SOA # Zone authority
dig example.com CNAME # Alias
dig example.com SRV # Service location
dig -x IP # Reverse (PTR)
# Output Options
+short # Minimal output
+noall +answer # Just answer section
+trace # Follow delegation
+dnssec # Include DNSSEC
+multiline # Readable format
+nocmd +noall +answer # Scripting format
# Server Selection
dig @8.8.8.8 domain # Google DNS
dig @1.1.1.1 domain # Cloudflare
dig @10.50.1.90 domain # Internal (bind-01)
# Common Patterns
dig +short domain A | head -1 # Single IP
dig +short domain MX | sort -n # MX by priority
dig +short _dmarc.domain TXT # DMARC policy
dig +short sel._domainkey.domain TXT # DKIM key
dig +short domain TXT | grep spf # SPF record
dig @ns domain AXFR # Zone transfer
# Infrastructure Checks
dig _kerberos._tcp.domain SRV # AD Kerberos
dig _ldap._tcp.domain SRV # AD LDAP
dig +nssearch domain # Check all NS
dig +trace domain # Full resolution path
# Timing
+time=N # Timeout seconds
+tries=N # Retry count
+stats # Show timing
# Common Exit Codes
# 0 = Success (including NXDOMAIN)
# 1 = Usage error
# 8 = Connection refused
# 9 = Timeout