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