DNS concepts, record types, and management skill: Cloudflare, Route 53, DNSSEC, debugging with dig/nslookup, TTL strategy, and split-horizon DNS. USE WHEN: - Adding or modifying DNS records for a web application, mail system, or API - Configuring Cloudflare proxy (orange cloud) or DNS-only mode - Setting up Route 53 hosted zones, health checks, and routing policies - Debugging DNS propagation, CNAME loops, MX failures, or SPF/DKIM issues - Planning a migration with minimal downtime (TTL strategy) - Configuring DNSSEC, CAA records, or reverse DNS (PTR) - Setting up split-horizon DNS for internal vs external resolution DO NOT USE FOR: - SSL/TLS certificate issuance (use ssl-tls skill) - Nginx or application-level routing (use nginx or api-gateway skill) - Service mesh internal DNS (use kubernetes or service-mesh skill) - Email server configuration beyond DNS records (use a dedicated email skill)

GitHub
Install command
npx skhub add claude-dev-suite/dns
Markdown
SKILL.md

DNS — Concepts, Records, and Management

Record Type Reference

TypeFormatExample valueUse caseKey notes
AIPv4 address93.184.216.34Map hostname to IPv4Most common record; can have multiple values for round-robin
AAAAIPv6 address2606:2800:220:1:248:1893:25c8:1946Map hostname to IPv6Dual-stack: both A and AAAA on same name
CNAMEFully-qualified hostnamemyapp.netlify.app.Alias one name to anotherCannot be used at zone apex; trailing dot = absolute FQDN
MXPriority + hostname10 mail.example.com.Mail server routingLower priority number = higher preference; hostname must resolve via A/AAAA
TXTQuoted string"v=spf1 include:_spf.google.com ~all"SPF, DKIM, DMARC, domain ownership verificationMultiple TXT records on same name are valid
SRVPriority Weight Port Target10 5 5060 sip.example.com.Service discovery (SIP, XMPP, game servers)Format: _service._proto.name e.g. _sip._tcp.example.com
CAAFlags Tag Value0 issue "letsencrypt.org"Restrict which CAs can issue certs for domainProtects against misissued certificates
PTRFully-qualified hostnamehost.example.com.Reverse DNS (IP → hostname)Managed in in-addr.arpa zone; controlled by IP owner (ISP/cloud provider)
NSNameserver hostnamens1.cloudflare.com.Authoritative nameservers for zoneSet at registrar; changes propagate slowly (24-48h)
SOASerial Refresh Retry Expire Minimum(auto-managed)Zone metadata; used in AXFR transfersSerial must increment on every change for NOTIFY to work
ALIAS / ANAMEHostnamemyapp.netlify.app.Apex CNAME equivalentCloudflare: CNAME Flattening; Route 53: ALIAS record; resolves at zone apex
DSKey tag Algorithm Digest type Digest12345 13 2 ABC123...DNSSEC — links parent zone to child zone signing keyCreated in parent zone; references KSK in child

TTL Strategy

TTL (Time to Live, in seconds) controls how long resolvers cache a record. Higher TTL = fewer queries to your nameservers; lower TTL = faster propagation of changes.

Standard TTL Values

TTLSecondsUse case
1 minute60Active incident response / testing
5 minutes300Pre-migration staging
1 hour3600Default for most records
1 day86400Stable records (MX, NS)
1 week604800Static infrastructure

Migration TTL Strategy (Zero-Downtime Record Change)

Day -7:   Lower TTL of the record from 86400 to 300 (5 minutes)
          Wait for caches to expire (1 full day at old TTL = 86400s)

Day 0:    Change the record to the new value
          Old value expires from caches within 300s (5 minutes)
          New value takes effect almost immediately

Day +1:   Restore TTL to 86400 once confident in new value

This pattern ensures no resolver is caching the old value when you make the switch.


Cloudflare Setup for a Typical Web App

# Step-by-step zone configuration sequence

1. Add site to Cloudflare (free plan works for most apps)
   → Cloudflare scans existing records (import them)

2. Update NS records at your registrar to point to Cloudflare nameservers:
   ns1.cloudflare.com
   ns2.cloudflare.com
   (NS propagation: 24-48h)

3. Configure records in Cloudflare dashboard:

   Type  Name    Content              Proxy status   TTL
   A     @       93.184.216.34        Proxied (🟠)   Auto
   A     www     93.184.216.34        Proxied (🟠)   Auto
   CNAME api     backend.example.com  Proxied (🟠)   Auto
   MX    @       10 mail.example.com  DNS only (⬜)  Auto
   TXT   @       v=spf1 ...           DNS only (⬜)  Auto
   TXT   _dmarc  v=DMARC1; ...        DNS only (⬜)  Auto

4. SSL/TLS mode: Full (strict) — requires valid cert on origin
   Security > Settings: Enable HSTS (after app is stable)
   Speed > Optimization: Enable Brotli compression

5. Page rules (legacy) or Rules > Redirect rules:
   http://example.com/* → https://example.com/$1 (301)

Cloudflare Proxy (Orange Cloud) vs DNS-Only (Grey Cloud)

SettingIP exposedDDoS protectionCloudflare CDN/cacheWebSocketsUse when
Proxied (orange)Cloudflare IPs shownYesYesSupportedPublic web, APIs, static sites
DNS-only (grey)Origin IP exposedNoNoN/AMail (MX targets must be DNS-only), non-HTTP services, internal servers

Important: A record being proxied hides your origin IP, but only if you never expose your origin IP elsewhere (e.g., in certificate transparency logs, email headers, or old DNS records).


Route 53 — Key Concepts

# Install AWS CLI v2
pip install awscli --upgrade
aws configure  # Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, region

# List all hosted zones
aws route53 list-hosted-zones --output table

# Get hosted zone ID for example.com
ZONE_ID=$(aws route53 list-hosted-zones-by-name \
    --dns-name example.com \
    --query 'HostedZones[0].Id' \
    --output text | cut -d/ -f3)

# Upsert a record using change batch JSON
aws route53 change-resource-record-sets \
    --hosted-zone-id "$ZONE_ID" \
    --change-batch '{
        "Changes": [{
            "Action": "UPSERT",
            "ResourceRecordSet": {
                "Name": "api.example.com",
                "Type": "A",
                "TTL": 300,
                "ResourceRecords": [{"Value": "93.184.216.34"}]
            }
        }]
    }'

# Create an ALIAS record (apex CNAME equivalent) pointing to an ALB
aws route53 change-resource-record-sets \
    --hosted-zone-id "$ZONE_ID" \
    --change-batch '{
        "Changes": [{
            "Action": "UPSERT",
            "ResourceRecordSet": {
                "Name": "example.com",
                "Type": "A",
                "AliasTarget": {
                    "HostedZoneId": "Z35SXDOTRQ7X7K",
                    "DNSName": "my-alb-123.us-east-1.elb.amazonaws.com.",
                    "EvaluateTargetHealth": true
                }
            }
        }]
    }'

Route 53 Routing Policies

PolicyUse caseNotes
SimpleSingle resourceDefault; no health checks
WeightedA/B testing, canary deploymentsSet weights 0-255; 0 = no traffic
LatencyMulti-region lowest-latency routingAWS measures latency between client and region
FailoverActive-passive HARequires health check on primary record
GeolocationServe different content by country/continentFalls back to default if no match
Multivalue AnswerPoor-man's load balancing with health checksReturns up to 8 healthy records

dig — Complete Command Reference

# Basic query (A record)
dig example.com

# Short output (just the answer)
dig +short example.com
dig +short example.com A

# Query a specific record type
dig example.com MX
dig example.com TXT
dig example.com AAAA
dig example.com NS
dig example.com SOA
dig example.com CAA
dig example.com SRV

# Query a specific nameserver (bypass system resolver)
dig @8.8.8.8 example.com A         # Google Public DNS
dig @1.1.1.1 example.com A         # Cloudflare
dig @ns1.cloudflare.com example.com A  # Query authoritative NS directly

# Trace the full delegation chain from root servers
dig +trace example.com

# Check TXT records (SPF, DKIM, DMARC)
dig +short example.com TXT
dig +short _dmarc.example.com TXT
dig +short mail._domainkey.example.com TXT

# Reverse DNS lookup (PTR)
dig -x 93.184.216.34
dig +short -x 93.184.216.34

# Check DNSSEC chain
dig +dnssec example.com
dig +sigchase example.com A  # Validate DNSSEC chain (requires dig ≥ 9.9)

# Show all sections (answer, authority, additional)
dig +all example.com

# Check propagation against multiple resolvers
for ns in 8.8.8.8 1.1.1.1 9.9.9.9 208.67.222.222; do
    echo "--- $ns ---"
    dig @$ns +short example.com A
done

# Query SRV record (format: _service._proto.domain)
dig _https._tcp.example.com SRV

SPF / DKIM / DMARC Quick Reference

SPF TXT record

"v=spf1 ip4:203.0.113.0/24 include:_spf.google.com include:sendgrid.net ~all"
  • ip4: — explicitly authorise IP range
  • include: — include another domain's SPF (counts toward the 10 DNS lookup limit)
  • ~all — soft fail (mark as spam); -all = hard fail (reject)
  • +all — allow all (useless for security)

DKIM TXT record (example with key)

Name:    mail._domainkey.example.com
Value:   "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ..."

Generated by your email provider; p= is the public key.

DMARC TXT record

Name:    _dmarc.example.com
Value:   "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; ruf=mailto:dmarc@example.com; pct=100"
  • p=none → monitor only (start here)
  • p=quarantine → send to spam
  • p=reject → reject failing mail (fully enforced)
  • rua= → aggregate report address
  • pct=100 → apply to 100% of mail

Split-Horizon DNS

Split-horizon (or split-brain) DNS returns different answers for the same hostname depending on the source of the query — typically internal vs external.

Implementation with systemd-resolved (Linux client)

# /etc/systemd/resolved.conf.d/split-horizon.conf
[Resolve]
DNS=10.0.0.53          # Internal DNS server for internal zone
Domains=~internal.example.com ~corp.internal

Implementation with Bind9 (server-side)

# /etc/bind/named.conf.local
view "internal" {
    match-clients { 10.0.0.0/8; 172.16.0.0/12; localhost; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/example.com.internal";
    };
};

view "external" {
    match-clients { any; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/example.com.external";
    };
};

Internal zone can return private IPs (10.x.x.x) for api.example.com while the external zone returns the public IP or load balancer.


DNS Propagation: How It Works

When you update a record, the authoritative nameserver immediately has the new value. But:

  1. Your ISP's recursive resolver cached the old value for up to TTL seconds.
  2. Other resolvers around the world have their own cached copies.
  3. "DNS propagation" = waiting for all caches to expire their copies.

True propagation time = the TTL that was set before you made the change.

Check propagation across global resolvers:


Anti-Patterns

Anti-patternWhy it's harmfulFix
CNAME at zone apex (@)RFC 1034 violation; breaks NS and SOA records; some resolvers reject itUse ALIAS/ANAME (Cloudflare CNAME Flattening) or Route 53 ALIAS record at apex
TTL too high during migrationOld value cached for days; impossible to roll back quicklyLower TTL to 300s at least one full TTL cycle before any planned migration
No CAA recordAny CA can issue a cert for your domain; misissued certs possibleAdd CAA 0 issue "letsencrypt.org" and optionally CAA 0 iodef "mailto:security@example.com"
SPF with too many DNS lookups (> 10)Receivers fail SPF validation with permerror — mail gets rejected or marked spamFlatten SPF using tools like dmarcian SPF Surveyor; replace include: chains with ip4:/ip6:
DMARC p=none left in place indefinitelyMonitoring-only mode never enforces; DMARC provides no protectionProgress to p=quarantine then p=reject over 2-4 weeks after reviewing aggregate reports
Wildcard *.example.com without base domain recordSubdomain takeover risk; attacker registers a dangling CNAME targetEnsure wildcard doesn't mask legitimate subdomains; add specific records where needed
PTR record not set for mail server IPMany mail servers reject or spam-score mail from IPs without matching PTR (rDNS)Request PTR record from your hosting provider / ISP pointing to mail.example.com
Proxying all records through Cloudflare including mailMX records and their targets must resolve to origin; Cloudflare proxy on MX targets breaks SMTPSet MX records and their A record targets to DNS-only (grey cloud)
NS records changed at both registrar and zoneConflicting NS records cause intermittent resolution failuresNS records at registrar (glue records) must match NS records in the zone; change only at registrar
Changing NS without checking current TTLOld NS cached at resolvers for up to 48h; some queries go to old nameserversLower NS TTL to 300s before changing, or accept 24-48h propagation

Troubleshooting

SymptomLikely causeDiagnostic / Fix
CNAME loopA points to B which points back to Adig +trace example.com; trace the CNAME chain; break the loop at the registrar
MX not resolvingMX target is a CNAME (RFC violation) or MX target has no A/AAAA recordMX must point to a hostname with an A/AAAA record, not a CNAME
SPF "permerror" / too many lookupsChain of include: directives exceeds 10 DNS lookupsCount with MXToolbox SPF check; flatten include: chains to ip4:/ip6:
DKIM verification failureKey mismatch, key too short, or selector name wrongdig +short mail._domainkey.example.com TXT; compare with key in email header DKIM-Signature:
DNS not propagating after record changeResolver still serving cached old valueWait for TTL; test with dig @8.8.8.8 +short name; check TTL was previously lowered
dig +trace shows SERVFAILDNSSEC validation failure, NS record mismatch, or zone transfer issueCheck DS record in parent zone; verify DNSSEC signatures; dig +dnssec +multiline example.com DNSKEY
Subdomain returns wrong IP internallySplit-horizon not configured; internal hosts resolving via public DNSSet internal DNS server for the zone; configure clients to use internal resolver for that domain
NXDOMAIN for www but @ workswww record not added when moving to new DNS providerAdd CNAME www @ or A www <IP> explicitly; import all records when switching
Mail rejected: "550 No PTR record"Sending mail server IP has no reverse DNS (PTR)Request PTR from hosting provider; PTR must match mail server's FQDN
Cloudflare 1xxx error after enabling proxyOrigin IP block, origin firewall blocking Cloudflare IPs, or SSL mode mismatchWhitelist Cloudflare IP ranges on origin; check SSL mode is "Full (strict)" not "Flexible"
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/infrastructure/dns

Default branch

main

Latest commit

9496306

Tree SHA

fe4e2f1